如何在Go语言中不将空结构体封送到JSON中?

w46czmvw  于 2023-03-27  发布在  Go
关注(0)|答案(5)|浏览(196)

我有一个这样的结构体:

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指针或接口值,以及任何长度为零的数组、切片、Map或字符串
但是没有考虑到一个全为空/默认值的结构体。它的所有字段也都用omitempty标记,但是这没有任何效果。
我怎样才能让JSON包 * 不 * 封送我的空结构体字段?

tvz2xvvm

tvz2xvvm1#

正如文档中所说,“any nil pointer.”--使结构体成为指针。指针有明显的“空”值:nil
修复-使用struct 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 */ }
pdkcd3nj

pdkcd3nj2#

正如@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来完成代码,所以我更喜欢内联结构,以避免混乱。

kgqe7b3p

kgqe7b3p3#

Data是一个初始化的结构体,所以它不被认为是空的,因为encoding/json只查看立即数,而不是结构体内部的字段。
遗憾的是,从json.Marshaler返回nil目前不起作用:

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

您也可以给予Result一个封送拆收器,但不值得这样做。
正如Matt建议的那样,唯一的选择是将Data作为指针,并将值设置为nil

8gsdolmq

8gsdolmq4#

有一个优秀的Golang proposal已经活跃了4年多,所以在这一点上,可以肯定的是,它不会很快成为标准库。正如@Matt指出的,传统的方法是将 * struct * 转换为 *pointers-to-struct *。如果这种方法不可行(或不切实际的),则替代方案是使用不支持 * 省略零值结构 * 的替代JSON编码器。
我创建了Golang json库的镜像(clarketm/json),并在应用omitempty标记时添加了对 * 省略零值结构 * 的支持。该库通过递归检查 public 结构字段,以与流行的YAML编码器go-yaml类似的方式检测 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"
}
zxlwwiss

zxlwwiss5#

omitempty不仅是解决方案,如果你想省略它,那么你必须替换代码

type Result struct {
    Data       MyStruct  `json:"data,omitempty"`
    Status     string    `json:"status,omitempty"`
    Reason     string    `json:"reason,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"`
}

上面的代码只允许您填写1个字段。
例如,如果你正在填充Data1,那么你不能填充Data2和Data3,它会抛出错误

相关问题