golang json自定义解析
1.使用背景
假设json格式为:
{
"op": "+",
"num1": 10,
"num2": 13
}
最终需要转换为:
{
"op": "+",
"result": 23
}
此时,golang的json的序列化是否支持呢?
2.自定义解析
golang encoding/json中支持用户自定义json序列化,只需要实现当前结构体的UnmarshalJSON/MarshalJSON。
例如:上述例子,这里以UnmarshalJSON为例。
// UnmarshalJSON
func (o *Operation) UnmarshalJSON(data []byte) error {
// type 定义新类型 避免内部循环引用 导致stack overflow
type opShadow Operation
var tmp opShadow
if err := json.Unmarshal([]byte(data), &tmp); err != nil {
return err
}
switch tmp.Op {
case "+":
o.Result = tmp.Num1 + tmp.Num2
case "-":
o.Result = tmp.Num1 - tmp.Num2
}
o.Op = tmp.Op
return nil
}
实际上就是在方法中实现自己的业务逻辑,注意内部使用了Unmarshal,会导致鸡生蛋,蛋生鸡的问题,谁依赖谁,一直死循环依赖,最终导致stack overflow,因此在内部需要type
一个类型,用这个别名类型来操作。
3.使用编译时断言
在上述使用代码中,我们添加:
var _ json.Unmarshaler = (*Operation)(nil
便可以在编译时看到自己写的UnmarshalJSON接口是否正确,如果没写/不正确,此时会报错,非常好用。
在c++中我们也是秉持编译时优先抛出问题,golang也是如此,使用编译时的断言简单好用,还可以避免一些错误问题。
4.嵌入式结构体
针对嵌入式结构体的UnmarshalJSON使用,我们往往会出错,例如:
var testJSON = `{"num":5,"duration":"5s"}`
type Nested struct {
Dur time.Duration `json:"duration"`
}
func (n *Nested) UnmarshalJSON(data []byte) error {
*n = Nested{}
tmp := struct {
Dur string `json:"duration"`
}{}
fmt.Printf("parsing nested json %s \n", string(data))
if err := json.Unmarshal(data, &tmp); err != nil {
fmt.Printf("failed to parse nested: %v", err)
return err
}
tmpDur, err := time.ParseDuration(tmp.Dur)
if err != nil {
fmt.Printf("failed to parse duration: %v", err)
return err
}
(*n).Dur = tmpDur
return nil
}
type Object struct {
Nested
Num int `json:"num"`
}
func (o *Object) UnmarshalJSON(data []byte) error {
*o = Object{}
tmp := struct {
Nested
Num int `json:"num"`
}{}
fmt.Printf("parsing object json %s \n", string(data))
if err := json.Unmarshal(data, &tmp); err != nil {
fmt.Printf("failed to parse object: %v", err)
return err
}
fmt.Printf("tmp object: %+v \n", tmp)
(*o).Num = tmp.Num
(*o).Nested = tmp.Nested
return nil
}
在这段代码中,我们最终想要做的是Unmarshal为Object,那么会调用UnmarshalJSON,在这个方法中,调用了Unmarshal,此时会去调用Nested的UnmarshalJSON,便会导致num解析不出来,为了解决这种问题,我们可以做两次解析,也就是潜入类型与本身成员拆分。
tmp := struct {
//Nested
Num int `json:"num"`
}{}
// unmarshal Nested alone
tmpNest := struct {
Nested
}{}
fmt.Printf("parsing object json %s \n", string(data))
if err := json.Unmarshal(data, &tmp); err != nil {
fmt.Printf("failed to parse object: %v", err)
return err
}
// the Nested impl UnmarshalJSON, so it should be unmarshaled alone
if err := json.Unmarshal(data, &tmpNest); err != nil {
fmt.Printf("failed to parse object: %v", err)
return err
}
至此,便可以解决嵌入类型Unmarshal的问题。
本节完