Golang/JSON | DisallowUnknownFields 和自定义结构的序列化方法

栏目: Go · 发布时间: 5年前

内容简介:从举个例子:在上面的例子中,

Go 1.10 起,标准库 encoding/json 提供了方法 func (*Decoder) DisallowUnknownFields 。调用该方法表示,当目标是一个结构,并且输入流中包含任何 不匹配 该结构的非忽略的导出字段时, Decoder 会返回一个错误。

举个例子:

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"strings"
)

func main() {
	var jsonStream = `{"Name": "Ed", "Text": "Knock knock."}`
	type Message struct {
		Name string
	}
	dec := json.NewDecoder(strings.NewReader(jsonStream))
	dec.DisallowUnknownFields()
	var m Message
	err := dec.Decode(&m)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s\n", m.Name)
}

在上面的例子中, jsonStream 定义了一个结构 Message 不存在的字段 "Text" 。接下来,声明一个 Decoder ,并且调用 DisallowUnknownFields 方法。

运行 会发现,反序列化失败:

2009/11/10 23:00:00 json: unknown field "Text"

如果结构 Message 中有一个忽略的导出 Text 字段,又会发生什么呢?稍微改动下上面的代码:

func main() {
	var jsonStream = `{"Name": "Ed", "Text": "Knock knock."}`
	type Message struct {
		Name string
		Text string `json:"-"` // 增加一个字段定义
	}
	dec := json.NewDecoder(strings.NewReader(jsonStream))
	dec.DisallowUnknownFields()
	var m Message
	err := dec.Decode(&m)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s\n", m.Name)
}

运行 ,得到同样的错误:

2009/11/10 23:00:00 json: unknown field "Text"

同理,如果 Text 字段变成未导出字段,也会出现相同的报错:

func main() {
	var jsonStream = `{"Name": "Ed", "Text": "Knock knock."}`
	type Message struct {
		Name string
		text string
	}
	dec := json.NewDecoder(strings.NewReader(jsonStream))
	dec.DisallowUnknownFields()
	var m Message
	err := dec.Decode(&m)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s\n", m.Name)
}

运行 验证一下吧。

Q2:如何判断某个字段是否存在?

Go 结构的零值导致了我们无法通过判断字段值是否等于某个值来确定 JSON 字符串中是否存在某个字段。

此外,如果将字段类型定义为指针的话,则无法区分该字段的值就是 null 的场景。

当然,我们并非无计可施。 encoding/json 允许我们为自定义结构定义序列化和反序列化方法。只要分别实现 MarshalJSON() ([]byte, error)UnmarshalJSON([]byte) error 方法即可。

来看下如何通过 MarshalJSON 方法来判断某个字段是否存在:

type Int struct {
	Valid bool // 表示是否为有效值
	Set   bool // 表示是否设置
	Value int
}

// 自定义反序列化方法
func (i *Int) UnmarshalJSON(data []byte) error {
	// 如果调用了该方法,说明设置了该值
	i.Set = true
	if string(data) == "null" {
		// 表明该字段的值为 null
		return nil
	}

	var temp int
	if err := json.Unmarshal(data, &temp); err != nil {
		return err
	}
	i.Value = temp
	i.Valid = true
	return nil
}

// 自定义序列化方法
func (i Int) MarshalJSON() ([]byte, error) {
	return []byte(strconv.Itoa(i.Value)), nil
}

在上面的例子中,我们定义了一个结构体 Int 来替代基本结构 int 。并且定义了两个字段来表示是否设置及是否有效。这样,在序列化之后,我们就可以通过这两个字段来检查了。

此外,因为不希望序列化后出现这两个布尔值,因此还需要自定义序列化方法 MarshalJSON

下面,简单测试一下:

type A struct{ Val Int }

func do(bytes []byte) (A, error) {
	var a struct{ Val Int }
	err := json.Unmarshal(bytes, &a)
	return a, err
}

func main() {
	notSet := []byte(`{}`)
	setNull := []byte(`{"val": null}`)
	setValid := []byte(`{"val": 123}`)
	setWrongType := []byte(`{"val": "123"}`)

	a, err := do(notSet)
	log.Printf("NotSet|set:%t|valid:%t|err: %v\n", a.Val.Set, a.Val.Valid, err)

	a, err = do(setNull)
	log.Printf("SetNull|set:%t|valid:%t|err: %v\n", a.Val.Set, a.Val.Valid, err)

	a, err = do(setValid)
	log.Printf("SetValid|set:%t|valid:%t|err: %v\n", a.Val.Set, a.Val.Valid, err)

	a, err = do(setWrongType)
	log.Printf("SetWrongType|set:%t|valid:%t|err: %v\n", a.Val.Set, a.Val.Valid, err)
}

可以 运行 一下查看结果。

Q3:如何让 omitempty 选项对自定义结构体生效?

如果对字段使用了 omitempty 选项,那么在序列化过程中,如果该字段具有零值(即 false、0、nil 指针、nil 接口值和任何空数组、空 slice、空 map 或者空字符串),那么会忽略该字段。

但是,当前最新的 Go 版本(Go)下,这个选项对于自定义结构是不生效的。

举个例子:

func main() {
	type Text struct {
		ID      int
		Content string
	}
	type Message struct {
		Name    string
		Content Text	`json:"content,omitempty"`
	}
	
	var m Message
	m.Name = "test"
	bytes, err := json.Marshal(m)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s\n", string(bytes))
}

在上面的例子中,我们定义了一个结构体 Message ,它包含一个字段 Content ,类型为自定义的 Text ,并打上 omitempty 选项。然后,声明一个实例并给 Message 的另一个字段赋值。接着序列化该实例。

期待序列化结果为 {"Name":"test"} 。但是 运行 之后却得到以下结果:

{"Name":"test","content":{"ID":0,"Content":""}}

查看 encoding/json 相关代码会发现,该库在字段使用了 omitempty 选项时,对于空值的判断确实仅限于文档中描述的:

// encode.go
func isEmptyValue(v reflect.Value) bool {
	switch v.Kind() {
	case reflect.Array, reflect.Map, reflect.Slice, reflect.String: // 空数组、空 Map、空 slice、空字符串
		return v.Len() == 0
	case reflect.Bool: // false
		return !v.Bool()
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: // 0
		return v.Int() == 0
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: // 0
		return v.Uint() == 0
	case reflect.Float32, reflect.Float64: // 0
		return v.Float() == 0
	case reflect.Interface, reflect.Ptr: // nil 指针、nil interface 值
		return v.IsNil()
	}
	return false
}

也就是说,此判断逻辑不适用于自定义结构(非指针的情况下)。并且, 除了在使用自定义结构的时候使用指针 ,没有其他任何方法可以让 omitempty 选项对自定义结构体生效!!

Issue 11939 提出并跟踪了这个问题。这个 Issue 从 2015 年 7 月份提出至今仍未有确定的解决时间 (`д´)


以上所述就是小编给大家介绍的《Golang/JSON | DisallowUnknownFields 和自定义结构的序列化方法》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

分布式服务架构:原理、设计与实战

分布式服务架构:原理、设计与实战

李艳鹏、杨彪 / 电子工业出版社 / 2017-8 / 89.00

《分布式服务架构:原理、设计与实战》全面介绍了分布式服务架构的原理与设计,并结合作者在实施微服务架构过程中的实践经验,总结了保障线上服务健康、可靠的最佳方案,是一本架构级、实战型的重量级著作。 《分布式服务架构:原理、设计与实战》以分布式服务架构的设计与实现为主线,由浅入深地介绍了分布式服务架构的方方面面,主要包括理论和实践两部分。理论上,首先介绍了服务架构的背景,以及从服务化架构到微服务架......一起来看看 《分布式服务架构:原理、设计与实战》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

URL 编码/解码
URL 编码/解码

URL 编码/解码

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具