114

我有一个这样的结构:

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 包封送我的空结构字段?

4

4 回答 4

174

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

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

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 */ }
于 2013-08-06T19:09:33.480 回答
21

正如@chakrit 在评论中提到的那样,您无法通过实现json.Marshaleron来实现它MyStruct,并且在使用它的每个结构上实现自定义 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 和代码完成功能,所以我更喜欢内联以避免混乱。

于 2016-10-28T09:38:18.020 回答
10

Data是一个初始化的结构,所以它不被认为是空encoding/json的,因为只看立即值,而不是结构内的字段。

不幸的是,niljson.Marshaler当前返回不起作用:

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

你也可以给Resultmarshaler,但这不值得。

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

于 2013-08-06T20:14:38.287 回答
4

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

我创建了 Golang json 库 ( clarketm/json ) 的镜像,添加了对在应用标记时省略零值结构的支持。omitempty该库以类似于流行的 YAML 编码器go-yaml的方式通过递归检查公共结构字段来检测

例如

$ 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"
}
于 2019-10-01T04:34:31.757 回答