结构体中interface类型字段如何正确赋值?

96 阅读3分钟

问题抽象

type RuntimeCanaryResp struct {
	IsExistCanary bool
	CanaryInfo    *canaryInfo
}

type canaryInfo struct {
	CanaryRatio int32
	CanaryData  interface{}
}

// 模拟一个复杂结构体
type Inner struct {
	A []int64
	B *string
	C map[string]float64
	D map[int64]*string
}

var jsonStr = []byte(`{"IsExistCanary":true,"CanaryInfo":{"CanaryRatio":100,"CanaryData":{"A":[1,2,3],"B":"hello","C":{"a":1.1,"b":2.2},"D":{"1":"hello","2":"world"}}}}`)

Json Marshal的时候CanaryData类型为 map[string]interface{} 预期是想Marshal的时候还原为 对应的结构体类型 比如 *Inner

实现1

首先具体化 RuntimeCanaryResp.CanaryInfo.CanaryData=&Inner{}

一次 marshal便可实现

副作用: IsExistCanary=false时 CanaryInfo不再为nil

type MyCoderV1 struct {
}

func (v1 MyCoderV1) Name() string {
    return "my_coder_v1"
}

func (v1 MyCoderV1) Marshal(v interface{}) ([]byte, error) {
    str, err := json.Marshal(v)
    return str, err
}

func (v1 MyCoderV1) Unmarshal(data []byte, v interface{}) error {
    vv, _ := v.(**RuntimeCanaryResp)
    *vv = &RuntimeCanaryResp{
        CanaryInfo: &canaryInfo{ // 副作用: IsExistCanary=false时 CanaryInfo!=nil
            CanaryData: &Inner{}, // 这里必须传一个初始化好的结构体 传入var inner *Inner 无效
        },
    }
    return json.Unmarshal(data, vv)
}

实现2

使用RawMessage 实现延迟marshal

RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding.

type RuntimeCanaryRespV2 struct {
    RuntimeCanaryResp
    CanaryInfo json.RawMessage // 使用RawMessage
}

type MyCoderV2 struct {
}

func (v2 MyCoderV2) Name() string {
    return "my_coder_v2"
}

func (v2 MyCoderV2) Marshal(v interface{}) ([]byte, error) {
    str, err := json.Marshal(v)
    return str, err
}

func (v2 MyCoderV2) Unmarshal(data []byte, v interface{}) error {
    vv, _ := v.(**RuntimeCanaryResp)
    var vvv *RuntimeCanaryRespV2
    if err := json.Unmarshal(data, &vvv); err != nil {
        return err
    }
    if vvv == nil {
        return nil
    }
    *vv = &vvv.RuntimeCanaryResp
    if vvv.CanaryInfo == nil {
        return nil
    }
    vvv.RuntimeCanaryResp.CanaryInfo = &canaryInfo{
        CanaryData: &Inner{},
    }
    return json.Unmarshal(vvv.CanaryInfo, &vvv.RuntimeCanaryResp.CanaryInfo)
}

RawMessage 本质

// encoding/json

// RawMessage is a raw encoded JSON value.
// It implements Marshaler and Unmarshaler and can
// be used to delay JSON decoding or precompute a JSON encoding. 
type RawMessage []byte

// MarshalJSON returns m as the JSON encoding of m.
func (m RawMessage) MarshalJSON() ([]byte, error) {
    if m == nil {
        return []byte("null"), nil
    }
    return m, nil
}

// UnmarshalJSON sets *m to a copy of data.
func (m *RawMessage) UnmarshalJSON(data []byte) error {
    if m == nil {
        return errors.New("json.RawMessage: UnmarshalJSON on nil pointer")
    }
    *m = append((*m)[0:0], data...)
    return nil
}

实现3

第一次 marshal 对应的类型为CanaryData为map[string]interface{}

第二次marshal初始化其具体类型 (*vv).CanaryInfo.CanaryData = &Inner{}

type MyCoderV3 struct {
}

func (j MyCoderV3) Name() string {
    return "my_coder_v3"
}

func (j MyCoderV3) Marshal(v interface{}) ([]byte, error) {
    str, err := json.Marshal(v)
    return str, err
}

func (j MyCoderV3) Unmarshal(data []byte, v interface{}) error {
    vv, _ := v.(**RuntimeCanaryResp)
    if err := json.Unmarshal(data, vv); err != nil {
        panic(err)
    }
    if (*vv).CanaryInfo != nil {
        (*vv).CanaryInfo.CanaryData = &Inner{}
    }
    return json.Unmarshal(data, vv)
}

实现4

第一次 marshal 对应的类型为CanaryData为map[string]interface{}

第二次使用mapstructure map[string]interface{}->结构体

type MyCoderV4 struct {
}

func (j MyCoderV4) Name() string {
    return "my_coder_v4"
}

func (j MyCoderV4) Marshal(v interface{}) ([]byte, error) {
    str, err := json.Marshal(v)
    return str, err
}

func (j MyCoderV4) Unmarshal(data []byte, v interface{}) error {
    vv, _ := v.(**RuntimeCanaryResp)
    if err := json.Unmarshal(data, vv); err != nil {
        panic(err)
    }

    inner := &Inner{}

    var metadata mapstructure.Metadata
    ms, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
        TagName:          "json",
        Metadata:         &metadata,
        Result:           &inner,
        WeaklyTypedInput: true, // strings to int/uint (base implied by prefix)
    })
    if err != nil {
        return err
    }
    err = ms.Decode((*vv).CanaryInfo.CanaryData)
    if err != nil {
        return err
    }
    (*vv).CanaryInfo.CanaryData = inner
    return nil
}

测试


var rstr = []byte(`{"IsExistCanary":true,"CanaryInfo":{"CanaryRatio":100,"CanaryData":{"A":[1,2,3],"B":"hello","C":{"a":1.1,"b":2.2},"D":{"1":"hello","2":"world"}}}}`)

func Benchmark_CoderV1(t *testing.B) {

	my := &MyCoderV1{}
	for i := 0; i < t.N; i++ {
		var resp *RuntimeCanaryResp
		_ = my.Unmarshal(rstr, &resp)
	}
}

func Benchmark_CoderV2(t *testing.B) {
	my := &MyCoderV2{}
	for i := 0; i < t.N; i++ {
		var resp *RuntimeCanaryResp
		_ = my.Unmarshal(rstr, &resp)
	}
}

func Benchmark_CoderV3(t *testing.B) {
	my := &MyCoderV3{}
	for i := 0; i < t.N; i++ {
		var resp *RuntimeCanaryResp
		_ = my.Unmarshal(rstr, &resp)
	}
}

func Benchmark_CoderV4(t *testing.B) {
	my := &MyCoderV4{}
	for i := 0; i < t.N; i++ {
		var resp *RuntimeCanaryResp
		_ = my.Unmarshal(rstr, &resp)
	}
}

func Benchmark_ALL(t *testing.B) {
	t.Run("[V1]", Benchmark_CoderV1)
	t.Run("[V2]", Benchmark_CoderV2)
	t.Run("[V3]", Benchmark_CoderV3)
	t.Run("[V4]", Benchmark_CoderV4)
	fmt.Println("\nV1:", " marshal一次 有副作用")
	fmt.Println("V2:", " 使用RawMessage")
	fmt.Println("V3:", " marshal两次")
	fmt.Println("V4:", " marshal一次 + mapstructure")
}
goos: linux
goarch: amd64
pkg: go_test/interface_
cpu: Intel(R) Xeon(R) Platinum 8260 CPU @ 2.40GHz
Benchmark_ALL
Benchmark_ALL/[V1]
Benchmark_ALL/[V1]-32             177175              6753 ns/op            1168 B/op         35 allocs/op
Benchmark_ALL/[V2]
Benchmark_ALL/[V2]-32             137023              8581 ns/op            1512 B/op         41 allocs/op
Benchmark_ALL/[V3]
Benchmark_ALL/[V3]-32              98545             12140 ns/op            2736 B/op         66 allocs/op
Benchmark_ALL/[V4]
Benchmark_ALL/[V4]-32              69026             16873 ns/op            5696 B/op        113 allocs/op

V1:  marshal一次 有副作用
V2:  使用RawMessage
V3:  marshal两次
V4:  marshal一次 + mapstructure