自定義Go Json的序列化方法

編譯自 Custom JSON Marshalling in Go

我們知道,通過tag,可以有條件地實現定製Go JSON序列化的方式,比如json:",omitempty", 當字段的值爲空的時候,我們可以在序列化後的數據中不包含這個值,而json:"-"可以直接不被JSON序列化,如果想被序列化key-,可以設置tag爲json:"-,",加個逗號。

如果你爲類型實現了MarshalJSON() ([]byte, error)UnmarshalJSON(b []byte) error方法,那麼這個類型在序列化反序列化時將採用你定製的方法。

這些都是我們常用的設置技巧。

如果臨時想爲一個struct增加一個字段的話,可以採用本譯文的技巧,臨時創建一個類型,通過嵌入原類型的方式來實現。他和JSON and struct composition in Go一文中介紹的技巧還不一樣(譯文和jsoniter-go擴展可以閱讀陶文的Golang 中使用 JSON 的一些小技巧)。JSON and struct composition in Go一文中是通過嵌入的方式創建一個新的類型,你序列化和反序列化的時候需要使用這個新類型,而本譯文中的方法是無痛改變原類型的MarshalJSON方式,採用Alias方式避免遞歸解析,確實是一種非常巧妙的方法。

以下是譯文:

Go的 encoding/json序列化strcut到JSON數據:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main
 
import (
"encoding/json"
"os"
"time"
)
 
type MyUser struct {
ID int64 `json:"id"`
Name string `json:"name"`
LastSeen time.Time `json:"lastSeen"`
}
 
func main() {
_ = json.NewEncoder(os.Stdout).Encode(
&MyUser{1, "Ken", time.Now()},
)
}

序列化的結果:

1
{"id":1,"name":"Ken","lastSeen":"2009-11-10T23:00:00Z"}

但是如果我們想改變一個字段的顯示結果我們要怎麼做呢?例如,我們想把LastSeen顯示爲unix時間戳。

最簡單的方式是引入另外一個輔助struct,在MarshalJSON中使用它進行正確的格式化:

1
2
3
4
5
6
7
8
9
10
11
func (u *MyUser) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
ID int64 `json:"id"`
Name string `json:"name"`
LastSeen int64 `json:"lastSeen"`
}{
ID: u.ID,
Name: u.Name,
LastSeen: u.LastSeen.Unix(),
})
}

這樣做當然沒有問題,但是如果有很多字段的話就會很麻煩,如果我們能把原始struct嵌入到新的struct中,並讓它繼承所有不需要改變的字段就太好了:

1
2
3
4
5
6
7
8
9
func (u *MyUser) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
LastSeen int64 `json:"lastSeen"`
*MyUser
}{
LastSeen: u.LastSeen.Unix(),
MyUser: u,
})
}

但是等等,問題是這個輔助struct也會繼承原始struct的MarshalJSON方法,這會導致這個方法進入無限循環中,最後堆棧溢出。

解決辦法就是爲原始類型起一個別名,別名會有原始struct所有的字段,但是不會繼承它的方法:

1
2
3
4
5
6
7
8
9
10
func (u *MyUser) MarshalJSON() ([]byte, error) {
type Alias MyUser
return json.Marshal(&struct {
LastSeen int64 `json:"lastSeen"`
*Alias
}{
LastSeen: u.LastSeen.Unix(),
Alias: (*Alias)(u),
})
}

同樣的技術也可以應用於UnmarshalJSON方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func (u *MyUser) UnmarshalJSON(data []byte) error {
type Alias MyUser
aux := &struct {
LastSeen int64 `json:"lastSeen"`
*Alias
}{
Alias: (*Alias)(u),
}
if err := json.Unmarshal(data, &aux); err != nil {
return err
}
u.LastSeen = time.Unix(aux.LastSeen, 0)
return nil
}
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章