Go 自定义 Json 序列化规则
回复“Go语言”即可获赠从入门到进阶共10本电子书
浮云游子意,落日故人情。
开发过程中,我们经常会使用 JSON 作为数据传输格式。而这离不开对 JSON 数据的编解码工作,在 Go 中,encoding/json 包提供了这些能力。
我们可以使用 encoding/json 包的 Encoder.Encode() 和 Marshal() 实现 Json 序列化,使用 Decoder.Decode() 和 Unmarshal() 实现 Json 反序列化。
示例如下
package main
import (
"encoding/json"
"os"
)
type Metric struct {
Name string `json:"name"`
Value int64 `json:"value"`
}
func main() {
_ = json.NewEncoder(os.Stdout).Encode(
[]*Metric{
{"vv", 12},
{"tz", 9},
{"ss", 82},
},
)
}
输出
[{"name":"vv","value":12},{"name":"tz","value":9},{"name":"ss","value":82}]
提出问题
以上述结构体 Metric 为例,它代表的是统计指标。其中 Name 是指标名,Value 是指标值。
假设程序接收外部 Json 数据时,存在指标值为浮点数的情况,如下所示。
func main() {
var metric Metric
err := json.Unmarshal([]byte(`{"name": "tq", "value": 13.14}`), &metric)
if err != nil {
panic(err)
}
fmt.Println(metric)
}
由于 Metric 结构体中定义的 Value 字段为 int64,此时对 Json 数据的反序列化将得到以下错误
panic: json: cannot unmarshal number 13.14 into Go struct field Metric.value of type int64
这种问题应该如何处理?我们能否在不改变原有结构体定义的情况下,处理这种情况。
解决方法
在 encoding/json 中,有两个非常重要的接口。
// Marshaler is the interface implemented by types that
// can marshal themselves into valid JSON.
type Marshaler interface {
MarshalJSON() ([]byte, error)
}
// Unmarshaler is the interface implemented by types
// that can unmarshal a JSON description of themselves.
// The input can be assumed to be a valid encoding of
// a JSON value. UnmarshalJSON must copy the JSON data
// if it wishes to retain the data after returning.
//
// By convention, to approximate the behavior of Unmarshal itself,
// Unmarshalers implement UnmarshalJSON([]byte("null")) as a no-op.
type Unmarshaler interface {
UnmarshalJSON([]byte) error
}
如果类型 T 实现了 Marshaler 或 Unmarshaler 接口方法,就能自定义了序列化和反序列化规则。
有了这个理解基础,那么对于上文中的问题,我们很自然地想到,让 Metric 结构体实现UnmarshalJSON()。
那么首先想到最简单的方式,是引入一个临时结构体:让其 Value 字段定义为 float64,其他字段维持和原结构体一致。
func (u *Metric) UnmarshalJSON(data []byte) error {
type tmp struct {
Name string `json:"name"`
Value float64 `json:"value"`
}
t := &tmp{
Name: u.Name,
Value: float64(u.Value),
}
err := json.Unmarshal(data, t)
if err != nil {
return nil
}
// 注意 Unmarshaler 接口定义的以下规则:
// UnmarshalJSON must copy the JSON data
// if it wishes to retain the data after returning.
u.Name = t.Name
u.Value = int64(t.Value)
return nil
}
这样做能够解决我们的问题,但并不优雅。
我们可以使用结构体的继承。这样,当结构体存在大量字段时,我们仅定义需要更改的字段即可。
func (u *Metric) UnmarshalJSON(data []byte) error {
t := &struct {
Value float64 `json:"value"`
*Metric
}{
Value: float64(u.Value),
Metric: u,
}
if err := json.Unmarshal(data, &t); err != nil {
return err
}
u.Value = int64(t.Value)
return nil
}
不过这样子会引出新的问题:继承原结构体的新 strcut 同样会继承原结构体的方法(UnmarshalJSON() 方法),这将无限循环,最终导致堆栈溢出。
fatal error: stack overflow
最佳解决方案是以结构体新类型,让新类型获得原始结构体的所有字段属性,但是却不会继承原有结构体的方法。
func (u *Metric) UnmarshalJSON(data []byte) error {
type AliasMetric Metric
t := &struct {
Value float64 `json:"value"`
*AliasMetric
}{
Value: float64(u.Value),
AliasMetric: (*AliasMetric)(u),
}
if err := json.Unmarshal(data, &t); err != nil {
return err
}
u.Value = int64(t.Value)
return nil
}
这样,就完美解决了我们的问题。
总结
在 Json 数据的处理中,我们可以通过为对象实现 Marshaler 或 Unmarshaler 接口方法,从而制定我们想要的序列化和反序列化规则。
本文通过一个简单的示例,展示了如何通过实现结构体的 UnmarshalJSON() 方法,而达到反序列化时更改字段属性的目的。
同理,我们可以为结构体定义 MarshalJSON() 方法,制定序列化规则,这就留给读者自行尝试了。
------------------- End -------------------
往期精彩文章推荐:
欢迎大家点赞,留言,转发,转载,感谢大家的相伴与支持
想加入Go学习群请在后台回复【入群】
万水千山总是情,点个【在看】行不行