go语言将struct Marshal()序列化成json,默认按照一定格式进行转换。可以实现Marshaler接口的MarshalJSON()方法,实现自定义序列化。反序列化实现Unmarshaler接口。
1、Marshal和Unmarshal使用
type dog struct {
Name string
Age int
}
type person struct {
Id string
Dogs []dog
}
dogs := []dog{dog{
Name: "一",
Age: 1,
}}
per := person{
Id: "小明",
Dogs: dogs,
}
s, _ := json.Marshal(per) //序列化成json格式的[]byte
fmt.Println(string(s)) //输出:{"Id":"小明","Dogs":[{"Name":"一","Age":1}]}
per2 := person{}
_ = json.Unmarshal(s, &per2) //将json反序列化放入结构体&per2中
fmt.Println(per) //输出:{小明 [{一 1}]}
注意:
- json.Marshal(per)中,结构体per的字段名必须大写,小写字段名返回的[]byte是nil。
- json.Unmarshal(s, &per2)中,&per2必须是指针且不是nil,否则err InvalidUnmarshalError。
2、序列化对应数据类型的编码
json格式有自己的数据格式规范,包括:
- string:必须要用双引号引起来
- number:数值,没有双引号
- object:对象,{ key:value }表示方式,可嵌套。
- List:数组,[ value,value ]
- boolean:true/false,没有双引号
- null:空值
Marshal和Unmarshal的Go语言数据类型和json格式的数据类型对应关系如下:
Go语言类型 | json类型 |
bool | boolean |
string | string |
int,unit,float,rune | number |
array,slice | List |
struct,map | object |
nil | null |
chan,func,complex* | 不支持 |
[]byte | Base64编码的string |
time.Time | RFC3339格式string |
[]byte和time.Time两种类型序列化较为特殊,对数据做格式编码,保存成json字符串形式。
序列化时对两种类型编码,反序列时对类型解码。编码格式如下:
[]byte:"5ZaE6Imv"
time.Time:"2022-09-05T11:04:40.3491328+08:00"
per := person{
Id: "小明",
T: time.Now(),
B: []byte("善良"),
}
s, _ := json.Marshal(per)
fmt.Println(string(s))
//{"Id":"小明","T":"2022-09-05T11:04:40.3491328+08:00","B":"5ZaE6Imv"}
per2 := person{}
_ = json.Unmarshal(s, &per2)
fmt.Println(per2)
//{小明 2022-09-05 11:04:40.3491328 +0800 CST [229 150 132 232 137 175]}
fmt.Println(string(per2.B))
//善良
3、自定义序列化格式
[]byte和time.Time类型序列化是格式编码后的值,大部分场景可能不是我们希望的格式。我们使用自定义格式进行序列化。
3.1、Marshaler和Unmarshaler接口
Marshal序列化,递归遍历对象V,如果V实现了Marshaler接口,调用接口下的MarshalJSON()方法生成JSON。
// Marshaler is the interface implemented by types that
// can marshal themselves into valid JSON.
type Marshaler interface {
MarshalJSON() ([]byte, error)
}
Unmarshal反序列化,如果对象V实现了Unmarshaler接口,调用接口下的UnmarshalJSON()方法获得对象V。
type Unmarshaler interface {
UnmarshalJSON([]byte) error
}
3.2、实现Marshaler接口设置序列化字段格式
自定义格式的字段,将[]btye定义成新类型ByteArr,time.Time定义成新类型NewTime。方便后面实现接口。
type ByteArr []byte
type NewTime time.Time
type dogg struct {
Name ByteArr
Time NewTime
}
自定义格式效果:
- []byte不使用base64编码,直接显示string形式。
- time显示格式:2020-09-09 18:11:34
ByteArr 实现Marshaler接口和Unmarshaler接口,自定义 []byte 序列化和反序列化原样显示(string格式)。
// MarshalJSON returns m as the JSON encoding of m.
func (m ByteArr) MarshalJSON() ([]byte, error) {
if m == nil {
return []byte("null"), nil
}
return m, nil
}
// UnmarshalJSON sets *m to a copy of data.
func (m *ByteArr) UnmarshalJSON(data []byte) error {
if m == nil {
return errors.New("UnmarshalJSON on nil pointer")
}
*m = append((*m)[0:0], data...)
return nil
}
给ByteArr实现了两个接口的方法,MarshalJSON()序列化时调用,UnmarshalJSON()反序列化时调用。两个方法内没有做Base64的编码,直接原样返回。 也可以在方法内转化成其他格式。到此自定义格式已经完成。
测试自定义 []byte 序列化
func main() {
d := dogg{
Name: []byte(`{"aa":"435","rt":23}`),
}
jsonD, err := json.Marshal(d)
fmt.Println(string(jsonD), err)
//{"Name":{"aa":"435","rt":23},"Time":{}} <nil>
d2 := dogg{}
err = json.Unmarshal(jsonD, &d2)
fmt.Println(string(d2.Name))
//{"aa":"435","rt":23}
d3 := dogg{
Name: []byte("会err吗"),
}
jsonD, err = json.Marshal(d3)
//json: error calling MarshalJSON for type main.ByteArr: invalid character 'ä'
//looking for beginning of value
fmt.Println(string(jsonD), err)
d4 := dogg{}
err = json.Unmarshal(jsonD, &d4)
fmt.Println(string(d4.Name))
}
测试结果:d 序列化成功,json字符串为 []byte 原始数据。
d3 序列化失败。 json: error calling MarshalJSON for type main.ByteArr: invalid character 'ä' looking for beginning of value
3.3、MarshalJSON() 方法报错分析
该方法要求返回一个有效的JSON。百度搜索到的答案说:需要给方法的返回值加双引号。修改代码如下:
// MarshalJSON returns m as the JSON encoding of m.
func (m ByteArr) MarshalJSON() ([]byte, error) {
s := string(m)
if m == nil {
//return []byte("null"), nil
s = "null"
}
st := "\"" + s + "\"" //加双引号
//st := strconv.Quote(s) //加双引号和转义\
return []byte(st), nil
}
//输出
//json: error calling MarshalJSON for type main.ByteArr: invalid character 'a' af
//ter top-level value
//{"Name":"会err吗","Time":{}} <nil>
//"会err吗"
只加双引号:d 序列化失败, json: error calling MarshalJSON for type main.ByteArr: invalid character 'a' after top-level value
d3 序列化成功。
加双引号并且转义\:序列化全部成功。
分析MarshalJSON()输出结果:
- 只加双引号,d=“{"aa":"435","rt":23}”,d3="会err吗" 。
- 加双引号并且转义\,d="{\"aa\":\"435\",\"rt\":23}",d3="会err吗"
有转义字符显然不是我们要的结果。
最终分析结果
MarshalJSON()方法的返回要求合法的JSON。合法的JSON不单单是网上说的使用双引号。
- 如果实现接口的对象是某个字段,则返回的JSON为该字段的value。返回值符合value的JSON格式。JSON的value包括string、number、list、boolean、object等,每个数据有不同的格式,string需要双引号。number没有双引号,必须是数值。object是个JSON。list是中括号列表等等。
- 如果实现接口是整个结构体对象,合法的JSON应该是完整的JSON对象。
因此,想要将 []byte 直接作为json的value显示,只需要满足JSON的value数据格式。
3.4、使用go语言json包中的 json.RawMessage 实现 []byte 直接json显示
json.RawMessage源码如下:
// RawMessage is a raw encoded JSON value.
// It implements Marshaler and Unmarshaler and can
// be used to delay JSON decoding or precompute a JSON encoding.
type RawMessage []byte
// MarshalJSON returns m as the JSON encoding of m.
func (m RawMessage) MarshalJSON() ([]byte, error) {
if m == nil {
return []byte("null"), nil
}
return m, nil
}
// UnmarshalJSON sets *m to a copy of data.
func (m *RawMessage) UnmarshalJSON(data []byte) error {
if m == nil {
return errors.New("json.RawMessage: UnmarshalJSON on nil pointer")
}
*m = append((*m)[0:0], data...)
return nil
}
json.RawMessage使用
type dogg struct {
Name json.RawMessage
Time NewTime
}
func main() {
d := dogg{
Name: []byte(`{"aa":"435","rt":23}`),
}
jsonD, err := json.Marshal(d)
fmt.Println(string(jsonD), err)
d2 := dogg{}
err = json.Unmarshal(jsonD, &d2)
fmt.Println(string(d2.Name))
}
//{"Name":{"aa":"435","rt":23},"Time":"0001-01-01 00:10:112"} <nil>
//{"aa":"435","rt":23}
3.5、实现TextMarshaler接口自定义序列化格式
序列化时,找不到 MarshalJSON() 函数,会继续判断是否实现TextMarshaler接口。是,则调用接口下的 MarshalText() 函数。反序列化时,继续调用 TextUnmarshaler.UnmarshalText() 函数。接口如下:
// TextMarshaler is the interface implemented by an object that can
// marshal itself into a textual form.
//
// MarshalText encodes the receiver into UTF-8-encoded text and returns the result.
type TextMarshaler interface {
MarshalText() (text []byte, err error)
}
// TextUnmarshaler is the interface implemented by an object that can
// unmarshal a textual representation of itself.
//
// UnmarshalText must be able to decode the form generated by MarshalText.
// UnmarshalText must copy the text if it wishes to retain the text
// after returning.
type TextUnmarshaler interface {
UnmarshalText(text []byte) error
}
MarshalJSON()和MarshalText()两个函数的区别:
- MarshalJSON返回的是合法的JSON,返回的值,Marshal() 函数在拼接json字符串时直接使用。实现接口的对象是最外层结构体对象,序列化就是函数返回值。是结构体中字段,返回值是序列化的json串的value值。
- MarshalText返回的是UTF-8的文本。序列化过程中,将函数返回值看作的json的字符串类型,Marshal() 函数会对返回值加双引号。实现接口对象是结构体字段,返回值被看作是序列化的json中value的值(字符串类型)。对象是最外层结构体,整个结构体序列化就是含双引号字符串。特殊字符默认转义。
本文的数据格式:
[]byte:全部是json格式的字符串。
time.Time:普通字符串,不含特殊字符。
3.6、最终实现版本
根据数据特点,对 []byte 类型选择实现 Marshaler 接口的MarshalJSON()方法。对 time.Time 类型选择实现 TextMarshaler 接口的 MarshalText() 方法。
type ByteArr []byte
type NewTime time.Time
type dogg struct {
Name ByteArr
Time NewTime
}
func (t NewTime) MarshalText() ([]byte, error) {
tm := time.Time(t)
if y := tm.Year(); y < 0 || y >= 10000 {
return nil, errors.New("Time.MarshalText: year outside of range [0,9999]")
}
return []byte(tm.Format("2006-01-02 15:04:05")), nil
}
func (t *NewTime) UnmarshalText(b []byte) error {
//必须是2006-01-02 15:04:05字符串
tm, _ := time.Parse("2006-01-02 15:04:05", string(b))
*t = NewTime(tm)
return nil
}
// MarshalJSON returns m as the JSON encoding of m.
func (m ByteArr) MarshalJSON() ([]byte, error) {
if m == nil {
return []byte("null"), nil
}
b := json.Valid(m) //判断是否是json格式(只有json的五种数据类型也合法,“a”,true,et)
if !b {
s := strconv.Quote(string(m)) //不是json,加双引号作为string返回
return []byte(s), nil
}
return m, nil
}
// UnmarshalJSON sets *m to a copy of data.
func (m *ByteArr) UnmarshalJSON(data []byte) error {
if m == nil {
return errors.New("UnmarshalJSON on nil pointer")
}
*m = append((*m)[0:0], data...)
return nil
}
func ByteMarshal() {
d := dogg{
Name: []byte(`{"aa":"435","rt":23}`),
Time: NewTime(time.Now()),
}
jsonD, err := json.Marshal(d)
fmt.Println(string(jsonD), err)
d2 := dogg{}
err = json.Unmarshal(jsonD, &d2)
fmt.Println(string(d2.Name), time.Time(d.Time).Format("2006-01-02 15:04:05"))
}
//{"Name":{"aa":"435","rt":23},"Time":"2022-09-06 15:17:45"} <nil>
//{"aa":"435","rt":23} 2022-09-06 15:17:45