我有一个这样的结构:
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 包不封送我的空结构字段?
正如文档所说,“任何 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 */ }
正如@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
,因此永远不会被省略。
Data
是一个已初始化的结构,因此它不被视为空,因为 encoding/json
只查看立即值,而不是结构内的字段。
遗憾的是,目前无法从 json.Marshaler
返回 nil
:
func (_ MyStruct) MarshalJSON() ([]byte, error) {
if empty {
return nil, nil // unexpected end of JSON input
}
// ...
}
您也可以给 Result
一个封送处理程序,但这不值得。
正如马特建议的那样,唯一的选择是使 Data
成为指针并将值设置为 nil
。
encoding/json
不能检查结构的子字段。这不会很有效,是的。但这当然不是不可能的。
json.Marshaler
来完成。
Result
类型本身上实现 json.Marshaler
,这可能非常不方便。
这个特性有一个出色的 Golang proposal,它已经活跃了 4 年多,所以在这一点上,可以肯定地假设它不会很快进入标准库。正如@Matt 所指出的,传统的 方法是将structs 转换为pointers-to-structs。如果这种方法不可行(或不切实际),那么另一种方法是使用支持省略零值结构的替代 json 编码器。
我创建了 Golang json 库 (clarketm/json) 的镜像,添加了在应用 omitempty
标记时省略零值结构的支持。该库检测 zeroness 的方式与 recursively checking the public struct fields 的流行 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"
}
&MyStruct{ /* values */ }
算作 nil 指针吗?该值不为零。Data
字段不为空,那将是编组为字节流而不是数据本身的内存地址乱码,这在大多数情况下不是有意的?