在go中自定义Json序列化

本文仅为翻译,原文链接->Custom JSON Marshalling in Go

Go自带一个包encoding/json,这个包使序列化结构体(struct)和反序列化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"}

但是,如果我想改变序列化之后其中一个field的值应该怎么做呢?比如,我们想要把LastSeen修改为Unix时间戳的形式。

一个简单的方法就是使用一个辅助的结构体并在调用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(),
})
}

这种方法当时使可以的,但是如果一个结构体有很多个字段field,那么这种方法就比较麻烦了(笨重.jpg)。最好可以直接把原来的结构体放进辅助结构体里,让辅助结构体继承所有不要被改变的field,只添加需要被改变的field。

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,
})
}

但是问题是,这个辅助的结构体同样会继承MarshalJSON这个方法,这样就会导致无限循环(其实就是MarshalJSON调用MarshalJSON)。

解决方法就是给原始的结构体做一个别名,这个别名会继承原始结构体的所有field但不会继承方法。

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
}