📜  golang json omitempty struct - Javascript (1)

📅  最后修改于: 2023-12-03 15:31:00.983000             🧑  作者: Mango

Golang 中的 JSON 忽略空值情况的结构体

在 Golang 中,我们经常需要将结构体转换成 JSON 格式的字符串,并发送到其他系统或者程序中。但是,有时候我们的结构体中的某些字段可能是可选的,也就是说有些时候这个字段可能没有值,为了减少网络传输和提高性能,我们可能需要忽略这些空值字段。本文将介绍在 Golang 中如何定义一个忽略空值的结构体。

定义一个结构体

首先我们需要定义一个结构体,例如:

type Person struct {
	Name   string `json:"name"`
	Age    int    `json:"age,omitempty"`
	Gender string `json:"gender"`
}

这里定义了一个 Person 的结构体,其中有三个字段,分别是 NameAgeGender。我们通过 json 标签来指定字段在 JSON 中的命名方式。特别的,omitempty 表示如果该字段的值为空,则忽略该字段,不包含在 JSON 字符串中。

转换成 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 中定义一个忽略空值情况的结构体,并自动忽略空值字段。注意,这个方法只适用于基本数据类型,不适用于非基本数据类型。对于非基本数据类型,我们可以通过定义自己的编码器来实现自动忽略空值字段的功能。