【问题标题】:How to not marshal an empty struct into JSON with Go?如何不使用 Go 将空结构编组为 JSON?
【发布时间】:2013-08-07 22:31:47
【问题描述】:

我有一个这样的结构:

type Result struct {
    Data       MyStruct  `json:"data,omitempty"`
    Status     string    `json:"status,omitempty"`
    Reason     string    `json:"reason,omitempty"`
}

但即使 MyStruct 的实例完全为空(意味着所有值都是默认值),它也会被序列化为:

"data":{}

我知道encoding/json 文档指定“空”字段是:

false,0,任何 nil 指针或接口值,以及任何数组, 长度为零的切片、映射或字符串

但不考虑所有空/默认值的结构。它的所有字段也都标有omitempty,但这没有任何作用。

如何让 JSON 包编组我的空结构字段?

【问题讨论】:

    标签: json go


    【解决方案1】:

    正如文档所说,“任何 nil 指针”。 -- 使结构成为指针。指针具有明显的“空”值:nil

    修复 - 使用结构 pointer 字段定义类型:

    type Result struct {
        Data       *MyStruct `json:"data,omitempty"`
        Status     string    `json:"status,omitempty"`
        Reason     string    `json:"reason,omitempty"`
    }
    

    然后是这样的值:

    result := Result{}
    

    将编组为:

    {}
    

    解释:注意我们类型定义中的*MyStruct。 JSON 序列化不关心它是否是一个指针——这是一个运行时细节。所以将结构字段变成指针只对编译和运行时有影响)。

    请注意,如果您确实将字段类型从 MyStruct 更改为 *MyStruct,您将需要指向结构值的指针来填充它,如下所示:

    Data: &MyStruct{ /* values */ }
    

    【讨论】:

    • 祝福你,马特,这就是我要找的东西
    • @Matt,你确定&MyStruct{ /* values */ } 算作零指针吗?该值不为零。
    • @Matt 是否有可能做出这种默认行为?我想总是省略。 (基本上不会在所有结构的每个字段中都使用标签)
    • 嗨,Matt,如果 Data 字段不为空,则将是内存地址乱码编组为字节流,而不是数据本身,这在大多数情况下不是有意的?
    【解决方案2】:

    正如@chakrit 在评论中提到的那样,您无法通过在MyStruct 上实现json.Marshaler 来实现此功能,并且在使用它的每个结构上实现自定义JSON 编组函数可能需要更多的工作。这实际上取决于您的用例是否值得额外的工作,或者您是否准备好在 JSON 中使用空结构,但这是我用于 Result 的模式:

    type Result struct {
        Data       MyStruct
        Status     string   
        Reason     string    
    }
    
    func (r Result) MarshalJSON() ([]byte, error) {
        return json.Marshal(struct {
            Data     *MyStruct   `json:"data,omitempty"`
            Status   string      `json:"status,omitempty"`
            Reason   string      `json:"reason,omitempty"`
        }{
            Data:   &r.Data,
            Status: r.Status,
            Reason: r.Reason,
        })        
    }
    
    func (r *Result) UnmarshalJSON(b []byte) error {
        decoded := new(struct {
            Data     *MyStruct   `json:"data,omitempty"`
            Status   string      `json:"status,omitempty"`
            Reason   string      `json:"reason,omitempty"`
        })
        err := json.Unmarshal(b, decoded)
        if err == nil {
            r.Data = decoded.Data
            r.Status = decoded.Status
            r.Reason = decoded.Reason
        }
        return err
    }
    

    如果您有包含许多字段的庞大结构,这可能会变得乏味,尤其是稍后更改结构的实现,但没有重写整个 json 包以满足您的需要(不是一个好主意),这几乎是唯一的我能想到的方式来完成这项工作,同时仍然在那里保留一个非指针 MyStruct

    此外,您不必使用内联结构,您可以创建命名结构。不过,我使用 LiteIDE 和代码完成功能,所以我更喜欢内联以避免混乱。

    【讨论】:

    • 这个答案没有意义。它解决不了任何问题。 &r.Data 永远不会是nil,所以它永远不会被省略。
    【解决方案3】:

    Data 是一个已初始化的结构,因此它不会被视为空,因为encoding/json 只查看立即值,而不是结构内的字段。

    很遗憾,目前无法从 json.Marshaler 返回 nil

    func (_ MyStruct) MarshalJSON() ([]byte, error) {
        if empty {
            return nil, nil // unexpected end of JSON input
        }
        // ...
    }
    

    您也可以给 Result 一个 marshaler,但不值得这样做。

    正如马特建议的那样,唯一的选择是将Data 设为指针并将值设置为nil

    【讨论】:

    • 我不明白为什么encoding/json 不能检查结构的子字段。这不会很有效,是的。但这当然不是不可能的。
    • @nemo 我明白你的意思,我改变了措辞。它不这样做,因为它没有效率。不过,可以根据具体情况使用json.Marshaler 来完成。
    • 通过在MyStruct 本身上实现json.Marshaler不可能决定MyStruct 是否为空。证明:play.golang.org/p/UEC8A3JGvx
    • 为此,您必须在包含 Result 的类型本身上实现 json.Marshaler,这可能非常不方便。
    【解决方案4】:

    这个特性有一个出色的 Golang proposal,它已经活跃了 4 年多,所以在这一点上,可以肯定地假设它不会很快进入标准库。正如@Matt 所指出的,传统的 方法是将structs 转换为pointers-to-structs。如果这种方法不可行(或不切实际),那么另一种方法是使用支持省略零值结构的替代 json 编码器。

    我创建了一个 Golang json 库 (clarketm/json) 的镜像,添加了在应用 omitempty 标记时省略零值结构的支持。该库以与流行的 YAML 编码器 go-yaml by recursively checking the public struct fields 类似的方式检测 zeroness

    例如

    $ go get -u "github.com/clarketm/json"
    
    import (
        "fmt"
        "github.com/clarketm/json" // drop-in replacement for `encoding/json`
    )
    
    type Result struct {
        Data   MyStruct `json:"data,omitempty"`
        Status string   `json:"status,omitempty"`
        Reason string   `json:"reason,omitempty"`
    }
    
    j, _ := json.Marshal(&Result{
        Status: "204",
        Reason: "No Content",
    })
    
    fmt.Println(string(j))
    
    // Note: `data` is omitted from the resultant json.
    {
      "status": "204"
      "reason": "No Content"
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-01-12
      • 1970-01-01
      • 1970-01-01
      • 2017-02-09
      • 2021-07-05
      • 2017-04-24
      • 2021-12-05
      • 2016-03-05
      相关资源
      最近更新 更多