开发过程中,我们经常会使用 json 作为数据传输格式。而这离不开对 json 数据的编解码工作,在 go 中,encoding/json 包提供了这些能力。
我们可以使用 encoding/json 包的 encoder.encode() 和 marshal() 实现 json 序列化,使用 decoder.decode() 和 unmarshal() 实现 json 反序列化。
示例如下
package mainimport ( "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() 方法,制定序列化规则,这就留给读者自行尝试了。
以上就是go 自定义 json 序列化规则的详细内容。