mirror of
https://github.com/XTLS/Xray-core.git
synced 2024-12-22 11:23:32 +02:00
API: Improve MarshalToJson() in common/reflect/marshal.go (#3655)
* Serialize enum to string in MarshalToJson(). * MarshalToJson() respect json tags. * Add insertTypeInfo parameter to MarshalToJson(). * Omit empty string in MarshalToJson(). * Serialize PortList to string in MarshalToJson(). --------- Co-authored-by: nobody <nobody@nowhere.mars>
This commit is contained in:
parent
4a1c0d7124
commit
ac628a9427
3 changed files with 148 additions and 41 deletions
|
@ -2,13 +2,17 @@ package reflect
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
cnet "github.com/xtls/xray-core/common/net"
|
||||
cserial "github.com/xtls/xray-core/common/serial"
|
||||
"github.com/xtls/xray-core/infra/conf"
|
||||
)
|
||||
|
||||
func MarshalToJson(v interface{}) (string, bool) {
|
||||
if itf := marshalInterface(v, true); itf != nil {
|
||||
func MarshalToJson(v interface{}, insertTypeInfo bool) (string, bool) {
|
||||
if itf := marshalInterface(v, true, insertTypeInfo); itf != nil {
|
||||
if b, err := json.MarshalIndent(itf, "", " "); err == nil {
|
||||
return string(b[:]), true
|
||||
}
|
||||
|
@ -16,7 +20,7 @@ func MarshalToJson(v interface{}) (string, bool) {
|
|||
return "", false
|
||||
}
|
||||
|
||||
func marshalTypedMessage(v *cserial.TypedMessage, ignoreNullValue bool) interface{} {
|
||||
func marshalTypedMessage(v *cserial.TypedMessage, ignoreNullValue bool, insertTypeInfo bool) interface{} {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -24,36 +28,67 @@ func marshalTypedMessage(v *cserial.TypedMessage, ignoreNullValue bool) interfac
|
|||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
r := marshalInterface(tmsg, ignoreNullValue)
|
||||
if msg, ok := r.(map[string]interface{}); ok {
|
||||
r := marshalInterface(tmsg, ignoreNullValue, insertTypeInfo)
|
||||
if msg, ok := r.(map[string]interface{}); ok && insertTypeInfo {
|
||||
msg["_TypedMessage_"] = v.Type
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func marshalSlice(v reflect.Value, ignoreNullValue bool) interface{} {
|
||||
func marshalSlice(v reflect.Value, ignoreNullValue bool, insertTypeInfo bool) interface{} {
|
||||
r := make([]interface{}, 0)
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
rv := v.Index(i)
|
||||
if rv.CanInterface() {
|
||||
value := rv.Interface()
|
||||
r = append(r, marshalInterface(value, ignoreNullValue))
|
||||
r = append(r, marshalInterface(value, ignoreNullValue, insertTypeInfo))
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func marshalStruct(v reflect.Value, ignoreNullValue bool) interface{} {
|
||||
func isNullValue(f reflect.StructField, rv reflect.Value) bool {
|
||||
if rv.Kind() == reflect.String && rv.Len() == 0 {
|
||||
return true
|
||||
} else if !isValueKind(rv.Kind()) && rv.IsNil() {
|
||||
return true
|
||||
} else if tag := f.Tag.Get("json"); strings.Contains(tag, "omitempty") {
|
||||
if !rv.IsValid() || rv.IsZero() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func toJsonName(f reflect.StructField) string {
|
||||
if tags := f.Tag.Get("protobuf"); len(tags) > 0 {
|
||||
for _, tag := range strings.Split(tags, ",") {
|
||||
if before, after, ok := strings.Cut(tag, "="); ok && before == "json" {
|
||||
return after
|
||||
}
|
||||
}
|
||||
}
|
||||
if tag := f.Tag.Get("json"); len(tag) > 0 {
|
||||
if before, _, ok := strings.Cut(tag, ","); ok {
|
||||
return before
|
||||
} else {
|
||||
return tag
|
||||
}
|
||||
}
|
||||
return f.Name
|
||||
}
|
||||
|
||||
func marshalStruct(v reflect.Value, ignoreNullValue bool, insertTypeInfo bool) interface{} {
|
||||
r := make(map[string]interface{})
|
||||
t := v.Type()
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
rv := v.Field(i)
|
||||
if rv.CanInterface() {
|
||||
ft := t.Field(i)
|
||||
name := ft.Name
|
||||
value := rv.Interface()
|
||||
tv := marshalInterface(value, ignoreNullValue)
|
||||
if tv != nil || !ignoreNullValue {
|
||||
if !ignoreNullValue || !isNullValue(ft, rv) {
|
||||
name := toJsonName(ft)
|
||||
value := rv.Interface()
|
||||
tv := marshalInterface(value, ignoreNullValue, insertTypeInfo)
|
||||
r[name] = tv
|
||||
}
|
||||
}
|
||||
|
@ -61,7 +96,7 @@ func marshalStruct(v reflect.Value, ignoreNullValue bool) interface{} {
|
|||
return r
|
||||
}
|
||||
|
||||
func marshalMap(v reflect.Value, ignoreNullValue bool) interface{} {
|
||||
func marshalMap(v reflect.Value, ignoreNullValue bool, insertTypeInfo bool) interface{} {
|
||||
// policy.level is map[uint32] *struct
|
||||
kt := v.Type().Key()
|
||||
vt := reflect.TypeOf((*interface{})(nil))
|
||||
|
@ -71,7 +106,7 @@ func marshalMap(v reflect.Value, ignoreNullValue bool) interface{} {
|
|||
rv := v.MapIndex(key)
|
||||
if rv.CanInterface() {
|
||||
iv := rv.Interface()
|
||||
tv := marshalInterface(iv, ignoreNullValue)
|
||||
tv := marshalInterface(iv, ignoreNullValue, insertTypeInfo)
|
||||
if tv != nil || !ignoreNullValue {
|
||||
r.SetMapIndex(key, reflect.ValueOf(&tv))
|
||||
}
|
||||
|
@ -87,27 +122,63 @@ func marshalIString(v interface{}) (r string, ok bool) {
|
|||
ok = false
|
||||
}
|
||||
}()
|
||||
|
||||
if iStringFn, ok := v.(interface{ String() string }); ok {
|
||||
return iStringFn.String(), true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func marshalKnownType(v interface{}, ignoreNullValue bool) (interface{}, bool) {
|
||||
func serializePortList(portList *cnet.PortList) (interface{}, bool) {
|
||||
if portList == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
n := len(portList.Range)
|
||||
if n == 1 {
|
||||
if first := portList.Range[0]; first.From == first.To {
|
||||
return first.From, true
|
||||
}
|
||||
}
|
||||
|
||||
r := make([]string, 0, n)
|
||||
for _, pr := range portList.Range {
|
||||
if pr.From == pr.To {
|
||||
r = append(r, pr.FromPort().String())
|
||||
} else {
|
||||
r = append(r, fmt.Sprintf("%d-%d", pr.From, pr.To))
|
||||
}
|
||||
}
|
||||
return strings.Join(r, ","), true
|
||||
}
|
||||
|
||||
func marshalKnownType(v interface{}, ignoreNullValue bool, insertTypeInfo bool) (interface{}, bool) {
|
||||
switch ty := v.(type) {
|
||||
case cserial.TypedMessage:
|
||||
return marshalTypedMessage(&ty, ignoreNullValue), true
|
||||
return marshalTypedMessage(&ty, ignoreNullValue, insertTypeInfo), true
|
||||
case *cserial.TypedMessage:
|
||||
return marshalTypedMessage(ty, ignoreNullValue), true
|
||||
return marshalTypedMessage(ty, ignoreNullValue, insertTypeInfo), true
|
||||
case map[string]json.RawMessage:
|
||||
return ty, true
|
||||
case []json.RawMessage:
|
||||
return ty, true
|
||||
case *json.RawMessage:
|
||||
return ty, true
|
||||
case json.RawMessage:
|
||||
case *json.RawMessage, json.RawMessage:
|
||||
return ty, true
|
||||
case *cnet.IPOrDomain:
|
||||
if domain := v.(*cnet.IPOrDomain); domain != nil {
|
||||
return domain.AsAddress().String(), true
|
||||
}
|
||||
return nil, false
|
||||
case *cnet.PortList:
|
||||
npl := v.(*cnet.PortList)
|
||||
return serializePortList(npl)
|
||||
case *conf.PortList:
|
||||
cpl := v.(*conf.PortList)
|
||||
return serializePortList(cpl.Build())
|
||||
case cnet.Address:
|
||||
if addr := v.(cnet.Address); addr != nil {
|
||||
return addr.String(), true
|
||||
}
|
||||
return nil, false
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
|
@ -138,9 +209,9 @@ func isValueKind(kind reflect.Kind) bool {
|
|||
}
|
||||
}
|
||||
|
||||
func marshalInterface(v interface{}, ignoreNullValue bool) interface{} {
|
||||
func marshalInterface(v interface{}, ignoreNullValue bool, insertTypeInfo bool) interface{} {
|
||||
|
||||
if r, ok := marshalKnownType(v, ignoreNullValue); ok {
|
||||
if r, ok := marshalKnownType(v, ignoreNullValue, insertTypeInfo); ok {
|
||||
return r
|
||||
}
|
||||
|
||||
|
@ -152,19 +223,27 @@ func marshalInterface(v interface{}, ignoreNullValue bool) interface{} {
|
|||
if k == reflect.Invalid {
|
||||
return nil
|
||||
}
|
||||
if isValueKind(k) {
|
||||
|
||||
if ty := rv.Type().Name(); isValueKind(k) {
|
||||
if k.String() != ty {
|
||||
if s, ok := marshalIString(v); ok {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// fmt.Println("kind:", k, "type:", rv.Type().Name())
|
||||
|
||||
switch k {
|
||||
case reflect.Struct:
|
||||
return marshalStruct(rv, ignoreNullValue)
|
||||
return marshalStruct(rv, ignoreNullValue, insertTypeInfo)
|
||||
case reflect.Slice:
|
||||
return marshalSlice(rv, ignoreNullValue)
|
||||
return marshalSlice(rv, ignoreNullValue, insertTypeInfo)
|
||||
case reflect.Array:
|
||||
return marshalSlice(rv, ignoreNullValue)
|
||||
return marshalSlice(rv, ignoreNullValue, insertTypeInfo)
|
||||
case reflect.Map:
|
||||
return marshalMap(rv, ignoreNullValue)
|
||||
return marshalMap(rv, ignoreNullValue, insertTypeInfo)
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
|
|
@ -6,11 +6,40 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/xtls/xray-core/common/protocol"
|
||||
. "github.com/xtls/xray-core/common/reflect"
|
||||
cserial "github.com/xtls/xray-core/common/serial"
|
||||
iserial "github.com/xtls/xray-core/infra/conf/serial"
|
||||
"github.com/xtls/xray-core/proxy/shadowsocks"
|
||||
)
|
||||
|
||||
func TestMashalAccount(t *testing.T) {
|
||||
account := &shadowsocks.Account{
|
||||
Password: "shadowsocks-password",
|
||||
CipherType: shadowsocks.CipherType_CHACHA20_POLY1305,
|
||||
}
|
||||
|
||||
user := &protocol.User{
|
||||
Level: 0,
|
||||
Email: "love@v2ray.com",
|
||||
Account: cserial.ToTypedMessage(account),
|
||||
}
|
||||
|
||||
j, ok := MarshalToJson(user, false)
|
||||
if !ok || strings.Contains(j, "_TypedMessage_") {
|
||||
|
||||
t.Error("marshal account failed")
|
||||
}
|
||||
|
||||
kws := []string{"CHACHA20_POLY1305", "cipherType", "shadowsocks-password"}
|
||||
for _, kw := range kws {
|
||||
if !strings.Contains(j, kw) {
|
||||
t.Error("marshal account failed")
|
||||
}
|
||||
}
|
||||
// t.Log(j)
|
||||
}
|
||||
|
||||
func TestMashalStruct(t *testing.T) {
|
||||
type Foo = struct {
|
||||
N int `json:"n"`
|
||||
|
@ -36,8 +65,8 @@ func TestMashalStruct(t *testing.T) {
|
|||
Arr: &arr,
|
||||
}
|
||||
|
||||
s, ok1 := MarshalToJson(f1)
|
||||
sp, ok2 := MarshalToJson(&f1)
|
||||
s, ok1 := MarshalToJson(f1, true)
|
||||
sp, ok2 := MarshalToJson(&f1, true)
|
||||
|
||||
if !ok1 || !ok2 || s != sp {
|
||||
t.Error("marshal failed")
|
||||
|
@ -69,7 +98,7 @@ func TestMarshalConfigJson(t *testing.T) {
|
|||
}
|
||||
|
||||
tmsg := cserial.ToTypedMessage(bc)
|
||||
tc, ok := MarshalToJson(tmsg)
|
||||
tc, ok := MarshalToJson(tmsg, true)
|
||||
if !ok {
|
||||
t.Error("marshal config failed")
|
||||
}
|
||||
|
@ -79,15 +108,14 @@ func TestMarshalConfigJson(t *testing.T) {
|
|||
keywords := []string{
|
||||
"4784f9b8-a879-4fec-9718-ebddefa47750",
|
||||
"bing.com",
|
||||
"DomainStrategy",
|
||||
"InboundTag",
|
||||
"Level",
|
||||
"Stats",
|
||||
"UserDownlink",
|
||||
"UserUplink",
|
||||
"System",
|
||||
"InboundDownlink",
|
||||
"OutboundUplink",
|
||||
"inboundTag",
|
||||
"level",
|
||||
"stats",
|
||||
"userDownlink",
|
||||
"userUplink",
|
||||
"system",
|
||||
"inboundDownlink",
|
||||
"outboundUplink",
|
||||
}
|
||||
for _, kw := range keywords {
|
||||
if !strings.Contains(tc, kw) {
|
||||
|
|
|
@ -17,7 +17,7 @@ func MergeConfigFromFiles(files []string, formats []string) (string, error) {
|
|||
return "", err
|
||||
}
|
||||
|
||||
if j, ok := creflect.MarshalToJson(c); ok {
|
||||
if j, ok := creflect.MarshalToJson(c, true); ok {
|
||||
return j, nil
|
||||
}
|
||||
return "", errors.New("marshal to json failed.").AtError()
|
||||
|
|
Loading…
Reference in a new issue