Browse Source

优化mesage序列化相关代码

pull/1/head
Sydonian 2 years ago
parent
commit
10061e6004
5 changed files with 673 additions and 4 deletions
  1. +7
    -0
      utils/serder/any_to_any.go
  2. +75
    -3
      utils/serder/serder.go
  3. +163
    -1
      utils/serder/serder_test.go
  4. +150
    -0
      utils/serder/walk_test.go
  5. +278
    -0
      utils/serder/walker.go

+ 7
- 0
utils/serder/any_to_any.go View File

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


+ 75
- 3
utils/serder/serder.go View File

@@ -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) {


+ 163
- 1
utils/serder/serder_test.go View File

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

+ 150
- 0
utils/serder/walk_test.go View File

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

+ 278
- 0
utils/serder/walker.go View File

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

Loading…
Cancel
Save