内容简介:从举个例子:在上面的例子中,
从 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 和自定义结构的序列化方法》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。