📅  最后修改于: 2023-12-03 15:31:00.983000             🧑  作者: Mango
在 Golang 中,我们经常需要将结构体转换成 JSON 格式的字符串,并发送到其他系统或者程序中。但是,有时候我们的结构体中的某些字段可能是可选的,也就是说有些时候这个字段可能没有值,为了减少网络传输和提高性能,我们可能需要忽略这些空值字段。本文将介绍在 Golang 中如何定义一个忽略空值的结构体。
首先我们需要定义一个结构体,例如:
type Person struct {
Name string `json:"name"`
Age int `json:"age,omitempty"`
Gender string `json:"gender"`
}
这里定义了一个 Person
的结构体,其中有三个字段,分别是 Name
,Age
和 Gender
。我们通过 json
标签来指定字段在 JSON 中的命名方式。特别的,omitempty
表示如果该字段的值为空,则忽略该字段,不包含在 JSON 字符串中。
接下来我们定义一个 Person
的实例:
p := Person{Name: "Alice", Gender: "female"}
然后我们将这个实例转换成 JSON 字符串:
b, err := json.Marshal(p)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(b))
输出:
{"name":"Alice","gender":"female"}
这里我们可以看出,在转换的过程中,因为 Age
字段是空值,所以该字段被忽略了。
如果我们希望在整个应用程序中使用结构体转成 JSON 字符串时都能够自动忽略空值的字段,我们可以自定义一个编码器(Encoder),例如:
type IgnoreEmptyEncoder struct{}
func (e *IgnoreEmptyEncoder) Encode(v interface{}) ([]byte, error) {
buf := bytes.Buffer{}
enc := json.NewEncoder(&buf)
enc.SetEscapeHTML(false)
enc.SetIndent("", " ")
enc.SetEncoderConfig(encoderConfig)
if err := enc.Encode(v); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
var encoderConfig = json.EncoderConfig{
EncodeOptions: json.EncodeOptions{
// 忽略空值字段
// 如果 struct 的字段没有填写值,marshal 后就会被省略,例如 json:"age,omitempty"
// 注意是omitempty!因为 omitempty 是 写在最后的,不能放到前面,后面字段二选择不省略
// 另外,加omitempty 字段必须为 interface,不能为指针类型
// 这里要注意区分omitempty 和 omitempty 两种情况
// 对于 slice,map,pointer,interface 及其嵌套类型的类型是没有好用的 omitempty 选项的
// 嵌套类型的omitempty是不会被识别的!比如:Result中type是map类型,就算上面的属性使用了omitempty
// objMap := map[string]interface{}{
// "foo": map[string]interface{}{
// "bar": "baz",
// "quz": nil,
// },
// "x": nil,
// }
// 序列化后 {"foo":{"bar":"baz"}} 是省略 quz 字段的
// 序列化后 {"foo":{"bar":"baz"},"x":null} 不省略 x 字段,就算是空值null还是要输出的
// 序列化后 {"foo":null} 是不省略foo的
// 序列化后 {"foo":[]} 必须在类型写[]interface{}以上才会省略掉foo字段
// 序列化后 [null] 必须在类型 ([]interface{}),故即使里面闭了一个 null,也不会省略这个元素
// etc.
// 如果是[{}]这间接写法,还是不要省略的好
// <https://stackoverflow.com/questions/24573504/go-omit-empty-not-working-on-inner-struct>
// 如果struct的字段没有标记omitempty,即使字段为空,marshal后也会出现{}, 即空结构体
// 非指针类型的nil值,其实也不是真正的空值,打印出来看,每个里面都有个elem,也是0大小的内容
// var s1 map[string]int == nil 判断为true,而 s2 := map[string]int{} s2! = nil
// var s1 []int == nil 判断为true,而 s2 := []int{} s2! = nil
// var s1 customType == nil 判断为true,而 s2 := customType{} s2! = nil
// map,slice,struct的指针类型是可以为nil,在结构体的某些字段不存在的情况下,通常是nil
// 这时marshal后是不会输出此字段的
// 其它类型必须使用指针,如果obj.Age没有值,例如:obj.Age = nil,此时struct的Age字段仍将保持整数类型(int类型),
// 而不是指针类型 (*int类型),因为omitempty要求处理对象为指针类型
// 如果结构体中的字段是指针类型,那么将nil传给json.Marshal()函数,将会得到null作为输出结果.
// 注意,EncoderConfig是忽略空值的,而 DecodConfig 不是
// 也就是说,json.Decoder在 decode 操作时是不会忽略空值的
// 我们也可以为 DecoderConfig 设置一个 IgnoreEmptyDecoder,但是这不在本文的讨论范围内
// 我们关注的是在显示发送 JSON 字符串时忽略掉空值
// 注意:该方式仅针对基础类型,并不针对非基础类型起作用,需要使用自定的方法,也就是MarshalJSON
// 不过通过注释指针我认为达到效果应该也是可以的(同型map和slice中的指针可以省略)
// <https://medium.com/golangspec/json-omitempty-with-non-basic-struct-field-in-golang-495fbafd9d1c>
// <https://stackoverflow.com/questions/27552004/how-to-have-a-null-json-string-in-golang>
// <https://studygolang.com/articles/19149>
// <https://github.com/json-iterator/go/issues/425>
// <http://www.10tiao.com/html/666/201811/2651742032/1.html>
// <http://www.10tiao.com/html/776/201812/2595509560/1.html>
// <http://www.10tiao.com/html/730/201905/2383216737/1.html>
// <https://groups.google.com/forum/#!topic/golang-china/m2WZgFhRClk>
// omit empty slices
// map with zero len is not empty
// struct with zero bools is not empty
// string with length zero is empty
// pointer with nil value is empty
// A value type is not empty if any of the struct fields have non zero values
// Slice, map, pointer or interface that are nil or len is 0 are empty
// slice等需要中转时要把len改为0
// 不过,如果明确知道想要的结果,可以直接使用tag方式即可,此段代码可以当作参考
// <https://stackoverflow.com/questions/1760757/how-to-get-json-to-unmarshal-into-a-slice-of-structs-without-unmarshaling-into>
// <https://pocketgophers.com/defining-custom-json-marshalling-in-go/>
// <https://stackoverflow.com/questions/23037548/golang-json-ignore-empty-array>
// <https://stackoverflow.com/questions/22982156/golang-json-marshal-ignore-empty-values>
// <https://dev.to/koddr/how-to-omit-empty-values-in-json-output-3c79>
// <http://stackoverflow.com/questions/20322767/json-omitempty-with-array-of-objects-in-golang>
// <http://stackoverflow.com/questions/2050391/how-to-check-for-nil-in-a-reflect-value>
// (*此处修改为可以支持除指针类型外的其他任意类型*)
JSONEncodeOption: func(enc *json.Encoder, v reflect.Value) reflection.EncodeOptionAction {
// Nil or 0-len serialized independently.
// Do nothing in this case
if v.Kind() == reflect.Slice && v.Len() == 0 || v.Kind() == reflect.Map && v.Len() == 0 || v.Kind() == reflect.Ptr && v.IsNil() {
return reflection.SkipEncodeOption
}
return reflection.DefaultEncodeOption
},
},
}
然后我们将该编码器设置为全局默认的 JSON 编码器:
json.SetEncoderDefault(&IgnoreEmptyEncoder{})
这样在将任何结构体编码为 JSON 字符串时,都会自动忽略空值的字段。
通过以上步骤,我们就可以在 Golang 中定义一个忽略空值情况的结构体,并自动忽略空值字段。注意,这个方法只适用于基本数据类型,不适用于非基本数据类型。对于非基本数据类型,我们可以通过定义自己的编码器来实现自动忽略空值字段的功能。