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:
nobody 2024-08-08 22:35:46 +08:00 committed by GitHub
parent 4a1c0d7124
commit ac628a9427
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 148 additions and 41 deletions

View file

@ -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
}