英文:
How to not marshal an empty struct into JSON with Go?
问题
我有一个这样的结构体:
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包不序列化我的空结构体字段?
英文:
I have a struct like this:
type Result struct {
Data MyStruct `json:"data,omitempty"`
Status string `json:"status,omitempty"`
Reason string `json:"reason,omitempty"`
}
But even if the instance of MyStruct is entirely empty (meaning, all values are default), it's being serialized as:
"data":{}
I know that the encoding/json docs specify that "empty" fields are:
> false, 0, any nil pointer or interface value, and any array,
> slice, map, or string of length zero
but with no consideration for a struct with all empty/default values. All of its fields are also tagged with omitempty
, but this has no effect.
How can I get the JSON package to not marshal my field that is an empty struct?
答案1
得分: 206
根据文档所说,“任何空指针。”-- 将结构体定义为指针。指针具有明显的“空”值: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 */ }
英文:
As the docs say, "any nil pointer." -- make the struct a pointer. Pointers have obvious "empty" values: nil
.
Fix - define the type with a struct pointer field:
type Result struct {
Data *MyStruct `json:"data,omitempty"`
Status string `json:"status,omitempty"`
Reason string `json:"reason,omitempty"`
}
Then a value like this:
result := Result{}
Will marshal as:
{}
Explanation: Notice the *MyStruct
in our type definition. JSON serialization doesn't care whether it is a pointer or not -- that's a runtime detail. So making struct fields into pointers only has implications for compiling and runtime).
Just note that if you do change the field type from MyStruct
to *MyStruct
, you will need pointers to struct values to populate it, like so:
Data: &MyStruct{ /* values */ }
答案2
得分: 23
如@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,所以我更喜欢内联以避免混乱。
英文:
As @chakrit mentioned in a comment, you can't get this to work by implementing json.Marshaler
on MyStruct
, and implementing a custom JSON marshalling function on every struct that uses it can be a lot more work. It really depends on your use case as to whether it's worth the extra work or whether you're prepared to live with empty structs in your JSON, but here's the pattern I use applied to 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
}
If you have huge structs with many fields this can become tedious, especially changing a struct's implementation later, but short of rewriting the whole json
package to suit your needs (not a good idea), this is pretty much the only way I can think of getting this done while still keeping a non-pointer MyStruct
in there.
Also, you don't have to use inline structs, you can create named ones. I use LiteIDE with code completion though, so I prefer inline to avoid clutter.
答案3
得分: 9
Data
是一个已初始化的结构体,所以它不被认为是空的,因为encoding/json
只查看立即值,而不是结构体内部的字段。
不幸的是,从json.Marshaler
返回nil
目前不起作用:
func (_ MyStruct) MarshalJSON() ([]byte, error) {
if empty {
return nil, nil // JSON输入意外结束
}
// ...
}
你可以给Result
也添加一个编组器,但这并不值得努力。
唯一的选择,正如Matt建议的那样,是将Data
设置为指针并将值设置为nil
。
英文:
Data
is an initialized struct, so it isn't considered empty because encoding/json
only looks at the immediate value, not the fields inside the struct.
Unfortunately, returning nil
from json.Marshaler
doesn't currently work:
func (_ MyStruct) MarshalJSON() ([]byte, error) {
if empty {
return nil, nil // unexpected end of JSON input
}
// ...
}
You could give Result
a marshaler as well, but it's not worth the effort.
The only option, as Matt suggests, is to make Data
a pointer and set the value to nil
.
答案4
得分: 6
这是一个关于Golang的提案,已经活跃了4年多,所以可以安全地假设它不会很快进入标准库。传统的方法是将结构体转换为指向结构体的指针。如果这种方法不可行(或不实际),那么另一种选择是使用支持省略零值结构体的替代json编码器。
我创建了一个Golang json库的镜像(clarketm/json),在应用omitempty
标签时,它支持省略零值结构体。该库通过类似于流行的YAML编码器go-yaml的方式(通过递归检查公共结构体字段)来检测零值。
示例:
$ go get -u "github.com/clarketm/json"
import (
"fmt"
"github.com/clarketm/json" // 用于替代 `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))
// 注意:结果的json中省略了 `data` 字段。
{
"status": "204",
"reason": "No Content"
}
英文:
There is an outstanding Golang proposal for this feature which has been active for over 4 years, so at this point, it is safe to assume that it will not make it into the standard library anytime soon. As @Matt pointed out, the traditional approach is to convert the structs to pointers-to-structs. If this approach is infeasible (or impractical), then an alternative is to use an alternate json encoder which does support omitting zero value structs.
I created a mirror of the Golang json library (clarketm/json) with added support for omitting zero value structs when the omitempty
tag is applied. This library detects zeroness in a similar manner to the popular YAML encoder go-yaml by recursively checking the public struct fields.
e.g.
$ 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"
}
答案5
得分: 0
omitempty
不仅仅是一个解决方案,如果你想要省略它,那么你需要将代码替换为:
type Result struct {
Data *MyStruct `json:"data,omitempty"`
Status string `json:"status,omitempty"`
Reason string `json:"reason,omitempty"`
}
现在,omitempty
将正常工作。谢谢...
另外,假设你想要保持类似于oneOf
的条件,意味着只允许填充一个字段到结构体中,那么代码将如下所示:
type Result struct {
Data1 *MyStruct1 `json:"Data1,omitempty"`
Data2 *Mystruct2 `json:"Data2,omitempty"`
Data3 *Mystruct3 `json:"Data3,omitempty"`
}
上述代码只允许你填充一个字段。例如,如果你填充了Data1,那么就不能填充Data2和Data3,否则会报错。
英文:
omitempty
is not only the solution if you want to omit it then you have replace code from
type Result struct {
Data MyStruct `json:"data,omitempty"`
Status string `json:"status,omitempty"`
Reason string `json:"reason,omitempty"`
}
To this,
type Result struct {
Data *MyStruct `json:"data,omitempty"`
Status string `json:"status,omitempty"`
Reason string `json:"reason,omitempty"`
}
Now omitempty will work properly
Thanks...
Additionally, suppose if you want to keep condition like oneOf
means only one field should be allowed to fill in stuct then the code would looks like,
type Result struct {
Data1 *MyStruct1 `json:"Data1,omitempty"`
Data2 *Mystruct2 `json:"Data2,omitempty"`
Data3 *Mystruct3 `json:"Data3,omitempty"`
}
The above code only allow you to fill only 1 field.
E.g if you are filling Data1 then you can't fill Data2 & Data3, it will throw error for that
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论