Go语言Marshal序列化遇到的问题

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:空值

MarshalUnmarshal的Go语言数据类型和json格式的数据类型对应关系如下:

Go语言类型json类型
boolboolean
stringstring
int,unit,float,runenumber
array,sliceList
struct,mapobject
nilnull
chan,func,complex*不支持
[]byteBase64编码的string
time.TimeRFC3339格式string

[]bytetime.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

4、除了结构体字段实现上面两个接口,还可以让整个结构体实现这两个接口。更新。。。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
您好,关于您的问题Go语言中使用标准库中的json包进行序列化和反序列化操作。具体实现方法如下: 1. 对于结构体的序列化和反序列化操作 首先定义一个结构体,该结构体中的字段必须是导出(首字母大写)的,方可被序列化和反序列化。 例如: ```go type Person struct { Name string `json:"name"` Age int `json:"age"` } ``` 在定义好结构体之后,即可使用json包中的Marshal函数进行序列化操作,使用Unmarshal函数进行反序列化操作。 例如: ```go // 序列化 p := Person{Name: "Tom", Age: 18} bytes, err := json.Marshal(p) if err != nil { // 处理错误 } fmt.Println(string(bytes)) // {"name":"Tom","age":18} // 反序列化 var person Person err = json.Unmarshal(bytes, &person) if err != nil { // 处理错误 } fmt.Println(person.Name) // Tom fmt.Println(person.Age) // 18 ``` 2. 对于map、slice等类型的序列化和反序列化操作 对于map、slice等类型,直接使用json包中的Marshal函数进行序列化操作,使用Unmarshal函数进行反序列化操作即可。 例如: ```go // map序列化 m := map[string]interface{}{ "name": "Tom", "age": 18, } bytes, err := json.Marshal(m) if err != nil { // 处理错误 } fmt.Println(string(bytes)) // {"age":18,"name":"Tom"} // map反序列化 var data map[string]interface{} err = json.Unmarshal(bytes, &data) if err != nil { // 处理错误 } fmt.Println(data["name"]) // Tom fmt.Println(data["age"]) // 18 // slice序列化 s := []string{"hello", "world"} bytes, err := json.Marshal(s) if err != nil { // 处理错误 } fmt.Println(string(bytes)) // ["hello","world"] // slice反序列化 var data []string err = json.Unmarshal(bytes, &data) if err != nil { // 处理错误 } fmt.Println(data[0]) // hello fmt.Println(data[1]) // world ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值