问题抽象
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