Go 自定義 Json 序列化規則

開發過程中,我們經常會使用 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() 方法,制定序列化規則,這就留給讀者自行嘗試了。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/MW2fXzRWHl5vxc15MHR_Bw