| @@ -13,6 +13,9 @@ type AnyToAnyOption struct { | |||
| NoFromAny bool // 不判断目的字段是否实现了FromAny接口 | |||
| NoToAny bool // 不判断源字段是否实现了ToAny接口 | |||
| Converters []Converter // 字段类型转换函数 | |||
| // 当目的类型为map[string]any,是否要递归的将源类型的每一个字段都变成map[string]any。 | |||
| // 注:字段的类型(而不是实际值的类型)必须为结构体或者结构体指针。 | |||
| RecursiveStructToMap bool | |||
| } | |||
| type FromAny interface { | |||
| @@ -43,6 +46,10 @@ func AnyToAny(src any, dst any, opts ...AnyToAnyOption) error { | |||
| hooks = append(hooks, c) | |||
| } | |||
| if opt.RecursiveStructToMap { | |||
| hooks = append(hooks, mp.RecursiveStructToMapHookFunc()) | |||
| } | |||
| config := &mp.DecoderConfig{ | |||
| TagName: "json", | |||
| Squash: true, | |||
| @@ -4,6 +4,7 @@ import ( | |||
| "encoding/json" | |||
| "fmt" | |||
| "reflect" | |||
| "strings" | |||
| ) | |||
| func ObjectToJSON(obj any) ([]byte, error) { | |||
| @@ -29,9 +30,80 @@ func MapToObject(m map[string]any, obj any) error { | |||
| } | |||
| func ObjectToMap(obj any) (map[string]any, error) { | |||
| var m map[string]any | |||
| err := AnyToAny(obj, &m) | |||
| return m, err | |||
| ctx := WalkValue(obj, func(ctx *WalkContext, event WalkEvent) WalkingOp { | |||
| switch e := event.(type) { | |||
| case StructBeginEvent: | |||
| mp := make(map[string]any) | |||
| ctx.StackPush(mp) | |||
| case StructArriveFieldEvent: | |||
| if !WillWalkInto(e.Value) { | |||
| ctx.StackPush(e.Value.Interface()) | |||
| } | |||
| case StructLeaveFieldEvent: | |||
| val := ctx.StackPop() | |||
| mp := ctx.StackPeek().(map[string]any) | |||
| jsonTag := e.Info.Tag.Get("json") | |||
| if jsonTag == "-" { | |||
| break | |||
| } | |||
| opts := strings.Split(jsonTag, ",") | |||
| keyName := opts[0] | |||
| if keyName == "" { | |||
| keyName = e.Info.Name | |||
| } | |||
| if contains(opts, "string", 1) { | |||
| val = fmt.Sprintf("%v", val) | |||
| } | |||
| mp[keyName] = val | |||
| case StructEndEvent: | |||
| case MapBeginEvent: | |||
| ctx.StackPush(make(map[string]any)) | |||
| case MapArriveEntryEvent: | |||
| if !WillWalkInto(e.Value) { | |||
| ctx.StackPush(e.Value.Interface()) | |||
| } | |||
| case MapLeaveEntryEvent: | |||
| val := ctx.StackPop() | |||
| mp := ctx.StackPeek().(map[string]any) | |||
| mp[fmt.Sprintf("%v", e.Key)] = val | |||
| case MapEndEvent: | |||
| case ArrayBeginEvent: | |||
| ctx.StackPush(make([]any, e.Value.Len())) | |||
| case ArrayArriveElementEvent: | |||
| if !WillWalkInto(e.Value) { | |||
| ctx.StackPush(e.Value.Interface()) | |||
| } | |||
| case ArrayLeaveElementEvent: | |||
| val := ctx.StackPop() | |||
| arr := ctx.StackPeek().([]any) | |||
| arr[e.Index] = val | |||
| case ArrayEndEvent: | |||
| } | |||
| return Next | |||
| }, WalkOption{ | |||
| StackValues: []any{make(map[string]any)}, | |||
| }) | |||
| return ctx.StackPop().(map[string]any), nil | |||
| } | |||
| func contains(arr []string, ele string, startIndex int) bool { | |||
| for i := startIndex; i < len(arr); i++ { | |||
| if arr[i] == ele { | |||
| return true | |||
| } | |||
| } | |||
| return false | |||
| } | |||
| func TypedMapToObject(m map[string]any, opt TypedSerderOption) (any, error) { | |||
| @@ -77,7 +77,7 @@ func (a DirToAnySt) ToAny(typ reflect.Type) (val any, ok bool, err error) { | |||
| return nil, false, nil | |||
| } | |||
| func Test_MapToObject(t *testing.T) { | |||
| func Test_AnyToAny(t *testing.T) { | |||
| Convey("包含用字符串保存的int数据", t, func() { | |||
| type Struct struct { | |||
| A string `json:"a"` | |||
| @@ -233,3 +233,165 @@ func Test_TypedMapToObject(t *testing.T) { | |||
| }) | |||
| } | |||
| func Test_MapToObject(t *testing.T) { | |||
| type Base struct { | |||
| Int int | |||
| Bool bool | |||
| String string | |||
| Float float32 | |||
| } | |||
| type ArraryStruct struct { | |||
| IntArr []int | |||
| StArr []Base | |||
| ArrArr [][]int | |||
| Nil []Base | |||
| } | |||
| type MapStruct struct { | |||
| StrMap map[string]string | |||
| StMap map[string]Base | |||
| MapMap map[string]map[string]string | |||
| Nil map[string]Base | |||
| } | |||
| type Top struct { | |||
| ArrSt ArraryStruct | |||
| MapSt *MapStruct | |||
| BaseIf any | |||
| NilPtr *Base | |||
| } | |||
| Convey("结构体递归转换成map[string]any", t, func() { | |||
| val := Top{ | |||
| ArrSt: ArraryStruct{ | |||
| IntArr: []int{1, 2, 3}, | |||
| StArr: []Base{ | |||
| { | |||
| Int: 1, | |||
| Bool: true, | |||
| String: "test", | |||
| Float: 1, | |||
| }, | |||
| { | |||
| Int: 2, | |||
| Bool: false, | |||
| String: "test2", | |||
| Float: 2, | |||
| }, | |||
| }, | |||
| ArrArr: [][]int{ | |||
| {1, 2, 3}, | |||
| {}, | |||
| nil, | |||
| }, | |||
| Nil: nil, | |||
| }, | |||
| MapSt: &MapStruct{ | |||
| StrMap: map[string]string{ | |||
| "a": "1", | |||
| "b": "2", | |||
| }, | |||
| StMap: map[string]Base{ | |||
| "a": { | |||
| Int: 1, | |||
| Bool: true, | |||
| String: "test", | |||
| Float: 1, | |||
| }, | |||
| "b": { | |||
| Int: 2, | |||
| Bool: false, | |||
| String: "test2", | |||
| Float: 2, | |||
| }, | |||
| }, | |||
| MapMap: map[string]map[string]string{ | |||
| "a": { | |||
| "a": "1", | |||
| "b": "2", | |||
| }, | |||
| "b": nil, | |||
| }, | |||
| Nil: nil, | |||
| }, | |||
| BaseIf: Base{ | |||
| Int: 1, | |||
| Bool: true, | |||
| String: "test", | |||
| Float: 1, | |||
| }, | |||
| NilPtr: nil, | |||
| } | |||
| retMp, err := ObjectToMap(val) | |||
| So(err, ShouldBeNil) | |||
| exceptMap := map[string]any{ | |||
| "ArrSt": map[string]any{ | |||
| "IntArr": []any{1, 2, 3}, | |||
| "StArr": []any{ | |||
| map[string]any{ | |||
| "Int": 1, | |||
| "Bool": true, | |||
| "String": "test", | |||
| "Float": 1, | |||
| }, | |||
| map[string]any{ | |||
| "Int": 2, | |||
| "Bool": false, | |||
| "String": "test2", | |||
| "Float": 2, | |||
| }, | |||
| }, | |||
| "ArrArr": []any{ | |||
| []any{1, 2, 3}, | |||
| []any{}, | |||
| []int(nil), | |||
| }, | |||
| "Nil": []Base(nil), | |||
| }, | |||
| "MapSt": map[string]any{ | |||
| "StrMap": map[string]any{ | |||
| "a": "1", | |||
| "b": "2", | |||
| }, | |||
| "StMap": map[string]any{ | |||
| "a": map[string]any{ | |||
| "Int": 1, | |||
| "Bool": true, | |||
| "String": "test", | |||
| "Float": 1, | |||
| }, | |||
| "b": map[string]any{ | |||
| "Int": 2, | |||
| "Bool": false, | |||
| "String": "test2", | |||
| "Float": 2, | |||
| }, | |||
| }, | |||
| "MapMap": map[string]any{ | |||
| "a": map[string]any{ | |||
| "a": "1", | |||
| "b": "2", | |||
| }, | |||
| "b": map[string]string(nil), | |||
| }, | |||
| "Nil": map[string]Base(nil), | |||
| }, | |||
| "BaseIf": map[string]any{ | |||
| "Int": 1, | |||
| "Bool": true, | |||
| "String": "test", | |||
| "Float": 1, | |||
| }, | |||
| "NilPtr": (*Base)(nil), | |||
| } | |||
| mpRetJson, err := ObjectToJSON(retMp) | |||
| So(err, ShouldBeNil) | |||
| exceptMapJson, err := ObjectToJSON(exceptMap) | |||
| So(err, ShouldBeNil) | |||
| So(string(mpRetJson), ShouldEqualJSON, string(exceptMapJson)) | |||
| }) | |||
| } | |||
| @@ -0,0 +1,150 @@ | |||
| package serder | |||
| import ( | |||
| "fmt" | |||
| "reflect" | |||
| "testing" | |||
| . "github.com/smartystreets/goconvey/convey" | |||
| myreflect "gitlink.org.cn/cloudream/common/utils/reflect" | |||
| ) | |||
| func Test_WalkValue(t *testing.T) { | |||
| type Base struct { | |||
| Int int | |||
| Bool bool | |||
| String string | |||
| Float float32 | |||
| } | |||
| type ArraryStruct struct { | |||
| IntArr []int | |||
| StArr []Base | |||
| ArrArr [][]int | |||
| Nil []Base | |||
| } | |||
| type MapStruct struct { | |||
| StrMap map[string]string | |||
| StMap map[string]Base | |||
| MapMap map[string]map[string]string | |||
| Nil map[string]Base | |||
| } | |||
| type Top struct { | |||
| ArrSt ArraryStruct | |||
| MapSt *MapStruct | |||
| BaseIf any | |||
| NilPtr *Base | |||
| } | |||
| isBaseDataType := func(val reflect.Value) bool { | |||
| typ := val.Type() | |||
| return typ == myreflect.TypeOf[int]() || typ == myreflect.TypeOf[bool]() || | |||
| typ == myreflect.TypeOf[string]() || typ == myreflect.TypeOf[float32]() || val.IsZero() | |||
| } | |||
| toString := func(val any) string { | |||
| return fmt.Sprintf("%v", val) | |||
| } | |||
| Convey("遍历", t, func() { | |||
| val := Top{ | |||
| ArrSt: ArraryStruct{ | |||
| IntArr: []int{1, 2, 3}, | |||
| StArr: []Base{ | |||
| { | |||
| Int: 1, | |||
| Bool: true, | |||
| String: "test", | |||
| Float: 1, | |||
| }, | |||
| { | |||
| Int: 2, | |||
| Bool: false, | |||
| String: "test2", | |||
| Float: 2, | |||
| }, | |||
| }, | |||
| ArrArr: [][]int{ | |||
| {1, 2, 3}, | |||
| {}, | |||
| nil, | |||
| }, | |||
| Nil: nil, | |||
| }, | |||
| MapSt: &MapStruct{ | |||
| StrMap: map[string]string{ | |||
| "a": "1", | |||
| "b": "2", | |||
| }, | |||
| StMap: map[string]Base{ | |||
| "a": { | |||
| Int: 1, | |||
| Bool: true, | |||
| String: "test", | |||
| Float: 1, | |||
| }, | |||
| "b": { | |||
| Int: 2, | |||
| Bool: false, | |||
| String: "test2", | |||
| Float: 2, | |||
| }, | |||
| }, | |||
| MapMap: map[string]map[string]string{ | |||
| "a": { | |||
| "a": "1", | |||
| "b": "2", | |||
| }, | |||
| "b": nil, | |||
| }, | |||
| Nil: nil, | |||
| }, | |||
| BaseIf: Base{ | |||
| Int: 1, | |||
| Bool: true, | |||
| String: "test", | |||
| Float: 1, | |||
| }, | |||
| NilPtr: nil, | |||
| } | |||
| var trace []string | |||
| WalkValue(val, func(ctx *WalkContext, event WalkEvent) WalkingOp { | |||
| switch e := event.(type) { | |||
| case StructBeginEvent: | |||
| trace = append(trace, "StructBeginEvent") | |||
| case StructArriveFieldEvent: | |||
| trace = append(trace, "StructFieldEvent", e.Info.Name) | |||
| if isBaseDataType(e.Value) { | |||
| trace = append(trace, toString(e.Value.Interface())) | |||
| } | |||
| case StructEndEvent: | |||
| trace = append(trace, "StructEndEvent") | |||
| case MapBeginEvent: | |||
| trace = append(trace, "MapBeginEvent") | |||
| case MapArriveEntryEvent: | |||
| trace = append(trace, "MapEntryEvent", e.Key.String()) | |||
| if isBaseDataType(e.Value) { | |||
| trace = append(trace, toString(e.Value.Interface())) | |||
| } | |||
| case MapEndEvent: | |||
| trace = append(trace, "MapEndEvent") | |||
| case ArrayBeginEvent: | |||
| trace = append(trace, "ArrayBeginEvent") | |||
| case ArrayArriveElementEvent: | |||
| trace = append(trace, "ArrayElementEvent", fmt.Sprintf("%d", e.Index)) | |||
| if isBaseDataType(e.Value) { | |||
| trace = append(trace, toString(e.Value.Interface())) | |||
| } | |||
| case ArrayEndEvent: | |||
| trace = append(trace, "ArrayEndEvent") | |||
| } | |||
| return Next | |||
| }) | |||
| So(trace, ShouldResemble, []string{}) | |||
| }) | |||
| } | |||
| @@ -0,0 +1,278 @@ | |||
| package serder | |||
| import ( | |||
| "reflect" | |||
| "github.com/zyedidia/generic/stack" | |||
| ) | |||
| type WalkEvent interface{} | |||
| type StructBeginEvent struct { | |||
| Value reflect.Value | |||
| } | |||
| type StructArriveFieldEvent struct { | |||
| Info reflect.StructField | |||
| Value reflect.Value | |||
| } | |||
| type StructLeaveFieldEvent struct { | |||
| Info reflect.StructField | |||
| Value reflect.Value | |||
| } | |||
| type StructEndEvent struct { | |||
| Value reflect.Value | |||
| } | |||
| type ArrayBeginEvent struct { | |||
| Value reflect.Value | |||
| } | |||
| type ArrayArriveElementEvent struct { | |||
| Index int | |||
| Value reflect.Value | |||
| } | |||
| type ArrayLeaveElementEvent struct { | |||
| Index int | |||
| Value reflect.Value | |||
| } | |||
| type ArrayEndEvent struct { | |||
| Value reflect.Value | |||
| } | |||
| type MapBeginEvent struct { | |||
| Value reflect.Value | |||
| } | |||
| type MapArriveEntryEvent struct { | |||
| Key reflect.Value | |||
| Value reflect.Value | |||
| } | |||
| type MapLeaveEntryEvent struct { | |||
| Key reflect.Value | |||
| Value reflect.Value | |||
| } | |||
| type MapEndEvent struct { | |||
| Value reflect.Value | |||
| } | |||
| type WalkingOp int | |||
| const ( | |||
| Next WalkingOp = iota | |||
| Skip | |||
| Stop | |||
| ) | |||
| type Walker func(ctx *WalkContext, event WalkEvent) WalkingOp | |||
| type WalkContext struct { | |||
| stack *stack.Stack[any] | |||
| } | |||
| func (c *WalkContext) StackPush(val any) { | |||
| c.stack.Push(val) | |||
| } | |||
| func (c *WalkContext) StackPop() any { | |||
| return c.stack.Pop() | |||
| } | |||
| func (c *WalkContext) StackPeek() any { | |||
| return c.stack.Peek() | |||
| } | |||
| type WalkOption struct { | |||
| StackValues []any | |||
| } | |||
| func WalkValue(value any, walker Walker, opts ...WalkOption) *WalkContext { | |||
| var opt WalkOption | |||
| if len(opts) > 0 { | |||
| opt = opts[0] | |||
| } | |||
| ctx := &WalkContext{ | |||
| stack: stack.New[any](), | |||
| } | |||
| for _, v := range opt.StackValues { | |||
| ctx.StackPush(v) | |||
| } | |||
| doWalking(ctx, reflect.ValueOf(value), walker) | |||
| return ctx | |||
| } | |||
| func doWalking(ctx *WalkContext, val reflect.Value, walker Walker) WalkingOp { | |||
| if !WillWalkInto(val) { | |||
| return Next | |||
| } | |||
| switch val.Kind() { | |||
| case reflect.Array: | |||
| fallthrough | |||
| case reflect.Slice: | |||
| if walker(ctx, ArrayBeginEvent{Value: val}) == Stop { | |||
| return Stop | |||
| } | |||
| for i := 0; i < val.Len(); i++ { | |||
| eleVal := val.Index(i) | |||
| op := walker(ctx, ArrayArriveElementEvent{ | |||
| Index: i, | |||
| Value: eleVal, | |||
| }) | |||
| if op == Skip { | |||
| if walker(ctx, ArrayLeaveElementEvent{ | |||
| Index: i, | |||
| Value: eleVal, | |||
| }) == Stop { | |||
| return Stop | |||
| } | |||
| continue | |||
| } | |||
| if op == Stop { | |||
| return Stop | |||
| } | |||
| if doWalking(ctx, eleVal, walker) == Stop { | |||
| return Stop | |||
| } | |||
| if walker(ctx, ArrayLeaveElementEvent{ | |||
| Index: i, | |||
| Value: eleVal, | |||
| }) == Stop { | |||
| return Stop | |||
| } | |||
| } | |||
| if walker(ctx, ArrayEndEvent{Value: val}) == Stop { | |||
| return Stop | |||
| } | |||
| case reflect.Map: | |||
| if walker(ctx, MapBeginEvent{Value: val}) == Stop { | |||
| return Stop | |||
| } | |||
| keys := val.MapKeys() | |||
| for _, key := range keys { | |||
| val := val.MapIndex(key) | |||
| op := walker(ctx, MapArriveEntryEvent{ | |||
| Key: key, | |||
| Value: val, | |||
| }) | |||
| if op == Skip { | |||
| if walker(ctx, MapLeaveEntryEvent{ | |||
| Key: key, | |||
| Value: val, | |||
| }) == Stop { | |||
| return Stop | |||
| } | |||
| continue | |||
| } | |||
| if op == Stop { | |||
| return Stop | |||
| } | |||
| if doWalking(ctx, val, walker) == Stop { | |||
| return Stop | |||
| } | |||
| if walker(ctx, MapLeaveEntryEvent{ | |||
| Key: key, | |||
| Value: val, | |||
| }) == Stop { | |||
| return Stop | |||
| } | |||
| } | |||
| if walker(ctx, MapEndEvent{Value: val}) == Stop { | |||
| return Stop | |||
| } | |||
| case reflect.Struct: | |||
| if walker(ctx, StructBeginEvent{Value: val}) == Stop { | |||
| return Stop | |||
| } | |||
| for i := 0; i < val.NumField(); i++ { | |||
| field := val.Field(i) | |||
| op := walker(ctx, StructArriveFieldEvent{ | |||
| Info: val.Type().Field(i), | |||
| Value: field, | |||
| }) | |||
| if op == Skip { | |||
| if walker(ctx, StructLeaveFieldEvent{ | |||
| Info: val.Type().Field(i), | |||
| Value: field, | |||
| }) == Stop { | |||
| return Stop | |||
| } | |||
| continue | |||
| } | |||
| if op == Stop { | |||
| return Stop | |||
| } | |||
| if doWalking(ctx, field, walker) == Stop { | |||
| return Stop | |||
| } | |||
| if walker(ctx, StructLeaveFieldEvent{ | |||
| Info: val.Type().Field(i), | |||
| Value: field, | |||
| }) == Stop { | |||
| return Stop | |||
| } | |||
| } | |||
| if walker(ctx, StructEndEvent{Value: val}) == Stop { | |||
| return Stop | |||
| } | |||
| case reflect.Interface: | |||
| fallthrough | |||
| case reflect.Pointer: | |||
| eleVal := val.Elem() | |||
| return doWalking(ctx, eleVal, walker) | |||
| } | |||
| return Next | |||
| } | |||
| const ( | |||
| WillWalkIntoTypeKinds = (1 << reflect.Array) | (1 << reflect.Map) | (1 << reflect.Slice) | (1 << reflect.Struct) | |||
| ) | |||
| func WillWalkInto(val reflect.Value) bool { | |||
| if val.IsZero() { | |||
| return false | |||
| } | |||
| typ := val.Type() | |||
| typeKind := typ.Kind() | |||
| if typeKind == reflect.Interface || typeKind == reflect.Pointer { | |||
| return WillWalkInto(val.Elem()) | |||
| } | |||
| return ((1 << typeKind) & WillWalkIntoTypeKinds) != 0 | |||
| } | |||