mirror of https://github.com/XTLS/Xray-core
Browse Source
* Add MarshalToJson(). * Add cmd arg -dump for printing out merged multiple json configs. --------- Co-authored-by: nobody <nobody@nowhere.mars>pull/2885/head^2
nobody
11 months ago
committed by
GitHub
6 changed files with 476 additions and 10 deletions
@ -0,0 +1,173 @@
|
||||
package reflect |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"reflect" |
||||
"slices" |
||||
|
||||
cserial "github.com/xtls/xray-core/common/serial" |
||||
) |
||||
|
||||
func MarshalToJson(v interface{}) (string, bool) { |
||||
if itf := marshalInterface(v, true); itf != nil { |
||||
if b, err := json.MarshalIndent(itf, "", " "); err == nil { |
||||
return string(b[:]), true |
||||
} |
||||
} |
||||
return "", false |
||||
} |
||||
|
||||
func marshalTypedMessage(v *cserial.TypedMessage, ignoreNullValue bool) interface{} { |
||||
tmsg, err := v.GetInstance() |
||||
if err != nil { |
||||
return nil |
||||
} |
||||
r := marshalInterface(tmsg, ignoreNullValue) |
||||
if msg, ok := r.(map[string]interface{}); ok { |
||||
msg["_TypedMessage_"] = v.Type |
||||
} |
||||
return r |
||||
} |
||||
|
||||
func marshalSlice(v reflect.Value, ignoreNullValue 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)) |
||||
} |
||||
} |
||||
return r |
||||
} |
||||
|
||||
func marshalStruct(v reflect.Value, ignoreNullValue 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 { |
||||
r[name] = tv |
||||
} |
||||
} |
||||
} |
||||
return r |
||||
} |
||||
|
||||
func marshalMap(v reflect.Value, ignoreNullValue bool) interface{} { |
||||
// policy.level is map[uint32] *struct
|
||||
kt := v.Type().Key() |
||||
vt := reflect.TypeOf((*interface{})(nil)) |
||||
mt := reflect.MapOf(kt, vt) |
||||
r := reflect.MakeMap(mt) |
||||
for _, key := range v.MapKeys() { |
||||
rv := v.MapIndex(key) |
||||
if rv.CanInterface() { |
||||
iv := rv.Interface() |
||||
tv := marshalInterface(iv, ignoreNullValue) |
||||
if tv != nil || !ignoreNullValue { |
||||
r.SetMapIndex(key, reflect.ValueOf(&tv)) |
||||
} |
||||
} |
||||
} |
||||
return r.Interface() |
||||
} |
||||
|
||||
func marshalIString(v interface{}) (r string, ok bool) { |
||||
defer func() { |
||||
if err := recover(); err != nil { |
||||
r = "" |
||||
ok = false |
||||
} |
||||
}() |
||||
|
||||
if iStringFn, ok := v.(interface{ String() string }); ok { |
||||
return iStringFn.String(), true |
||||
} |
||||
return "", false |
||||
} |
||||
|
||||
func marshalKnownType(v interface{}, ignoreNullValue bool) (interface{}, bool) { |
||||
switch ty := v.(type) { |
||||
case cserial.TypedMessage: |
||||
return marshalTypedMessage(&ty, ignoreNullValue), true |
||||
case *cserial.TypedMessage: |
||||
return marshalTypedMessage(ty, ignoreNullValue), true |
||||
case map[string]json.RawMessage: |
||||
return ty, true |
||||
case []json.RawMessage: |
||||
return ty, true |
||||
case *json.RawMessage: |
||||
return ty, true |
||||
case json.RawMessage: |
||||
return ty, true |
||||
default: |
||||
return nil, false |
||||
} |
||||
} |
||||
|
||||
var valueKinds = []reflect.Kind{ |
||||
reflect.Bool, |
||||
reflect.Int, |
||||
reflect.Int8, |
||||
reflect.Int16, |
||||
reflect.Int32, |
||||
reflect.Int64, |
||||
reflect.Uint, |
||||
reflect.Uint8, |
||||
reflect.Uint16, |
||||
reflect.Uint32, |
||||
reflect.Uint64, |
||||
reflect.Uintptr, |
||||
reflect.Float32, |
||||
reflect.Float64, |
||||
reflect.Complex64, |
||||
reflect.Complex128, |
||||
reflect.String, |
||||
} |
||||
|
||||
func isValueKind(kind reflect.Kind) bool { |
||||
return slices.Contains(valueKinds, kind) |
||||
} |
||||
|
||||
func marshalInterface(v interface{}, ignoreNullValue bool) interface{} { |
||||
|
||||
if r, ok := marshalKnownType(v, ignoreNullValue); ok { |
||||
return r |
||||
} |
||||
|
||||
rv := reflect.ValueOf(v) |
||||
if rv.Kind() == reflect.Ptr { |
||||
rv = rv.Elem() |
||||
} |
||||
k := rv.Kind() |
||||
if k == reflect.Invalid { |
||||
return nil |
||||
} |
||||
if isValueKind(k) { |
||||
return v |
||||
} |
||||
|
||||
switch k { |
||||
case reflect.Struct: |
||||
return marshalStruct(rv, ignoreNullValue) |
||||
case reflect.Slice: |
||||
return marshalSlice(rv, ignoreNullValue) |
||||
case reflect.Array: |
||||
return marshalSlice(rv, ignoreNullValue) |
||||
case reflect.Map: |
||||
return marshalMap(rv, ignoreNullValue) |
||||
default: |
||||
break |
||||
} |
||||
|
||||
if str, ok := marshalIString(v); ok { |
||||
return str |
||||
} |
||||
return nil |
||||
} |
@ -0,0 +1,187 @@
|
||||
package reflect_test |
||||
|
||||
import ( |
||||
"bytes" |
||||
"encoding/json" |
||||
"strings" |
||||
"testing" |
||||
|
||||
. "github.com/xtls/xray-core/common/reflect" |
||||
cserial "github.com/xtls/xray-core/common/serial" |
||||
iserial "github.com/xtls/xray-core/infra/conf/serial" |
||||
) |
||||
|
||||
func TestMashalStruct(t *testing.T) { |
||||
type Foo = struct { |
||||
N int `json:"n"` |
||||
Np *int `json:"np"` |
||||
S string `json:"s"` |
||||
Arr *[]map[string]map[string]string `json:"arr"` |
||||
} |
||||
|
||||
n := 1 |
||||
np := &n |
||||
arr := make([]map[string]map[string]string, 0) |
||||
m1 := make(map[string]map[string]string, 0) |
||||
m2 := make(map[string]string, 0) |
||||
m2["hello"] = "world" |
||||
m1["foo"] = m2 |
||||
|
||||
arr = append(arr, m1) |
||||
|
||||
f1 := Foo{ |
||||
N: n, |
||||
Np: np, |
||||
S: "hello", |
||||
Arr: &arr, |
||||
} |
||||
|
||||
s, ok1 := MarshalToJson(f1) |
||||
sp, ok2 := MarshalToJson(&f1) |
||||
|
||||
if !ok1 || !ok2 || s != sp { |
||||
t.Error("marshal failed") |
||||
} |
||||
|
||||
f2 := Foo{} |
||||
if json.Unmarshal([]byte(s), &f2) != nil { |
||||
t.Error("json unmarshal failed") |
||||
} |
||||
|
||||
v := (*f2.Arr)[0]["foo"]["hello"] |
||||
|
||||
if f1.N != f2.N || *(f1.Np) != *(f2.Np) || f1.S != f2.S || v != "world" { |
||||
t.Error("f1 not equal to f2") |
||||
} |
||||
} |
||||
|
||||
func TestMarshalConfigJson(t *testing.T) { |
||||
|
||||
buf := bytes.NewBufferString(getConfig()) |
||||
config, err := iserial.DecodeJSONConfig(buf) |
||||
if err != nil { |
||||
t.Error("decode JSON config failed") |
||||
} |
||||
|
||||
bc, err := config.Build() |
||||
if err != nil { |
||||
t.Error("build core config failed") |
||||
} |
||||
|
||||
tmsg := cserial.ToTypedMessage(bc) |
||||
tc, ok := MarshalToJson(tmsg) |
||||
if !ok { |
||||
t.Error("marshal config failed") |
||||
} |
||||
|
||||
// t.Log(tc)
|
||||
|
||||
keywords := []string{ |
||||
"4784f9b8-a879-4fec-9718-ebddefa47750", |
||||
"bing.com", |
||||
"DomainStrategy", |
||||
"InboundTag", |
||||
"Level", |
||||
"Stats", |
||||
"UserDownlink", |
||||
"UserUplink", |
||||
"System", |
||||
"InboundDownlink", |
||||
"OutboundUplink", |
||||
} |
||||
for _, kw := range keywords { |
||||
if !strings.Contains(tc, kw) { |
||||
t.Error("marshaled config error") |
||||
} |
||||
} |
||||
} |
||||
|
||||
func getConfig() string { |
||||
return `{ |
||||
"log": { |
||||
"loglevel": "debug" |
||||
}, |
||||
"stats": {}, |
||||
"policy": { |
||||
"levels": { |
||||
"0": { |
||||
"statsUserUplink": true, |
||||
"statsUserDownlink": true |
||||
} |
||||
}, |
||||
"system": { |
||||
"statsInboundUplink": true, |
||||
"statsInboundDownlink": true, |
||||
"statsOutboundUplink": true, |
||||
"statsOutboundDownlink": true |
||||
} |
||||
}, |
||||
"inbounds": [ |
||||
{ |
||||
"tag": "agentin", |
||||
"protocol": "http", |
||||
"port": 8080, |
||||
"listen": "127.0.0.1", |
||||
"settings": {} |
||||
}, |
||||
{ |
||||
"listen": "127.0.0.1", |
||||
"port": 10085, |
||||
"protocol": "dokodemo-door", |
||||
"settings": { |
||||
"address": "127.0.0.1" |
||||
}, |
||||
"tag": "api-in" |
||||
} |
||||
], |
||||
"api": { |
||||
"tag": "api", |
||||
"services": [ |
||||
"HandlerService", |
||||
"StatsService" |
||||
] |
||||
}, |
||||
"routing": { |
||||
"rules": [ |
||||
{ |
||||
"inboundTag": [ |
||||
"api-in" |
||||
], |
||||
"outboundTag": "api", |
||||
"type": "field" |
||||
} |
||||
], |
||||
"domainStrategy": "AsIs" |
||||
}, |
||||
"outbounds": [ |
||||
{ |
||||
"protocol": "vless", |
||||
"settings": { |
||||
"vnext": [ |
||||
{ |
||||
"address": "1.2.3.4", |
||||
"port": 1234, |
||||
"users": [ |
||||
{ |
||||
"id": "4784f9b8-a879-4fec-9718-ebddefa47750", |
||||
"encryption": "none" |
||||
} |
||||
] |
||||
} |
||||
] |
||||
}, |
||||
"tag": "agentout", |
||||
"streamSettings": { |
||||
"network": "ws", |
||||
"security": "none", |
||||
"wsSettings": { |
||||
"path": "/?ed=2048", |
||||
"headers": { |
||||
"Host": "bing.com" |
||||
} |
||||
} |
||||
} |
||||
} |
||||
] |
||||
}` |
||||
} |
Loading…
Reference in new issue