Go 自定義 Json 序列化規(guī)則
回復(fù)“Go語言”即可獲贈從入門到進(jìn)階共10本電子書
浮云游子意,落日故人情。
開發(fā)過程中,我們經(jīng)常會使用 JSON 作為數(shù)據(jù)傳輸格式。而這離不開對 JSON 數(shù)據(jù)的編解碼工作,在 Go 中,encoding/json 包提供了這些能力。
我們可以使用 encoding/json 包的 Encoder.Encode() 和 Marshal() 實(shí)現(xiàn) Json 序列化,使用 Decoder.Decode() 和 Unmarshal() 實(shí)現(xiàn) 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}]
提出問題
以上述結(jié)構(gòu)體 Metric 為例,它代表的是統(tǒng)計(jì)指標(biāo)。其中 Name 是指標(biāo)名,Value 是指標(biāo)值。
假設(shè)程序接收外部 Json 數(shù)據(jù)時(shí),存在指標(biāo)值為浮點(diǎn)數(shù)的情況,如下所示。
func?main()?{
?var?metric?Metric
?err?:=?json.Unmarshal([]byte(`{"name":?"tq",?"value":?13.14}`),?&metric)
?if?err?!=?nil?{
??panic(err)
?}
?fmt.Println(metric)
}
由于 Metric 結(jié)構(gòu)體中定義的 Value 字段為 int64,此時(shí)對 Json 數(shù)據(jù)的反序列化將得到以下錯(cuò)誤
panic:?json:?cannot?unmarshal?number?13.14?into?Go?struct?field?Metric.value?of?type?int64
這種問題應(yīng)該如何處理?我們能否在不改變原有結(jié)構(gòu)體定義的情況下,處理這種情況。
解決方法
在 encoding/json 中,有兩個(gè)非常重要的接口。
//?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 實(shí)現(xiàn)了 Marshaler 或 Unmarshaler 接口方法,就能自定義了序列化和反序列化規(guī)則。
有了這個(gè)理解基礎(chǔ),那么對于上文中的問題,我們很自然地想到,讓 Metric 結(jié)構(gòu)體實(shí)現(xiàn)UnmarshalJSON()。
那么首先想到最簡單的方式,是引入一個(gè)臨時(shí)結(jié)構(gòu)體:讓其 Value 字段定義為 float64,其他字段維持和原結(jié)構(gòu)體一致。
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 接口定義的以下規(guī)則:
?//?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
}
這樣做能夠解決我們的問題,但并不優(yōu)雅。
我們可以使用結(jié)構(gòu)體的繼承。這樣,當(dāng)結(jié)構(gòu)體存在大量字段時(shí),我們僅定義需要更改的字段即可。
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
}
不過這樣子會引出新的問題:繼承原結(jié)構(gòu)體的新 strcut 同樣會繼承原結(jié)構(gòu)體的方法(UnmarshalJSON() 方法),這將無限循環(huán),最終導(dǎo)致堆棧溢出。
fatal?error:?stack?overflow
最佳解決方案是以結(jié)構(gòu)體新類型,讓新類型獲得原始結(jié)構(gòu)體的所有字段屬性,但是卻不會繼承原有結(jié)構(gòu)體的方法。
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
}
這樣,就完美解決了我們的問題。
總結(jié)
在 Json 數(shù)據(jù)的處理中,我們可以通過為對象實(shí)現(xiàn) Marshaler 或 Unmarshaler 接口方法,從而制定我們想要的序列化和反序列化規(guī)則。
本文通過一個(gè)簡單的示例,展示了如何通過實(shí)現(xiàn)結(jié)構(gòu)體的 UnmarshalJSON() 方法,而達(dá)到反序列化時(shí)更改字段屬性的目的。
同理,我們可以為結(jié)構(gòu)體定義 MarshalJSON() 方法,制定序列化規(guī)則,這就留給讀者自行嘗試了。
-------------------?End?-------------------
往期精彩文章推薦:

歡迎大家點(diǎn)贊,留言,轉(zhuǎn)發(fā),轉(zhuǎn)載,感謝大家的相伴與支持
想加入Go學(xué)習(xí)群請?jiān)诤笈_回復(fù)【入群】
萬水千山總是情,點(diǎn)個(gè)【在看】行不行
