JSON反序列化嵌入式结构体

16

我希望将数据反序列化为定义如下的结构体Outer

type Outer struct {
    Inner
    Num int
}

type Inner struct {
    Data string
}
func (i *Inner) UnmarshalJSON(data []byte) error {
    i.Data = string(data)
    return nil
}

使用 json.Unmarshal(data, &Outer{}) 看起来只会使用 InnerUnmarshalJSON 方法,而忽略了 Num 字段: https://play.golang.org/p/WUBfzpheMl

我有一个笨重的解决方案,是手动设置 Num 字段,但我想知道是否有更简洁或更简单的方法。

谢谢!


1
请注意,原因在 Go 语言参考的这一部分中有解释:https://golang.org/ref/spec#Struct_types (查找“promoted”一词)。 - Cédric Van Rompay
这个回答更好地解释了这个有趣的互动,并且更加完整。 - Harish Ganesan
5个回答

4
这是因为Inner被嵌入到Outer中。这意味着当json库调用Outer的unmarshaler时,它实际上会在Inner上调用它。
因此,在func (i *Inner) UnmarshalJSON(data []byte)中,data参数包含整个json字符串,你只能处理Inner
你可以通过将Inner作为Outer的显式字段来解决这个问题。
Outer struct {
    I Inner // make Inner an explicit field
    Num int `json:"Num"`
}

Working example


感谢您的解释! - tochiai
15
当然,这解决了无法反序列化“Num”的问题,但它不能处理嵌入式数据,例如{"data": "test", "num": 1},它只能处理像{"inner": {"data": "test"}, "num": 1}这样的情况。 - Eric Chen
6
同意Eric的观点。我认为这个答案在这种情况下只适用于一定程度,但在一般情况下会产生误导。 - Mohammad
2
这是一个误导性的答案。我理解问题本身有点不清楚,但所提出的解决方案与嵌入式类型无关。 - maulik13

2
一种方法是完全放弃自定义的UnmarshalJSON函数,而只使用基本的JSON表示法,即:
type Outer struct {
    Inner
    Num int `json:"num"`
}

type Inner struct {
   Data string `json:"data"`
}

使用自定义取消编组方法,您可能会失去一些更细粒度的功能,但是当您取消编组具有大多数原始字段(如字符串)的结构时,您真的不需要担心这个问题。

在Go Playground中的示例

如果您确实需要自定义取消编组,可以使用组合,并为结构体提供自定义json编码标记,并使结构体包含要处理的字段。因此,如果data是将包含多个复杂字段的内容,则可以更改Inner以反映这些字段,如下所示:

type Outer struct {
    Data Inner `json:"data"`
    Num int `json:"num"`
}

type Inner struct {
    Thing string `json:"thing"`
    OtherThing int `json:"otherThing"`
}

在 Go playground 中的示例

再次强调,这里没有自定义的解封(unmarshalling)函数,但是可以轻松地为Inner编写一个。 (个人而言,在任何情况下都不会使用自定义解封函数,除非绝对必须使用解封功能,否则只需使用编码标签即可。)


这个完美地运作着 - undefined

2
面对同样的问题。一种解决方法是为每个子结构体进行两次反序列化。例如:
type Inner struct {
        Data string
}

type NumField struct {
        Num int
}

type Outer struct {
        Inner
        NumField
}

func (i *Inner) UnmarshalJSON(data []byte) error {
        i.Data = string(data)
        return nil
}

func (o *Outer) UnmarshalJSON(data []byte) error {
        if err := json.Unmarshal(data, &o.Inner); err != nil {
                return err
        }
        if err := json.Unmarshal(data, &o.NumField); err != nil {
                return err
        }
        return nil
}

func main() {
        x := Outer{}
        data := []byte(`{"Num": 4}`)
        _ = json.Unmarshal(data, &x)
        fmt.Printf("%#v\n", x)
}

这需要将额外的字段移到一个单独的结构体中。

2
同样的思路,解释更好:https://dev59.com/Xa7la4cB1Zd3GeqPbVYS#52305457 - balki

1

只需删除您的示例中的UnmarshalJSON,因为它用于反序列化Outer,由于Inner被内联。否则,如果要执行自定义操作,则需要覆盖它。

https://play.golang.org/p/D6V6vKpx9J


-1

实际上,您不需要显式字段,而是需要正确地进行编组/解组

例如:https://play.golang.org/p/mWPM7m44wfK

package main

import (
    "encoding/json"
    "fmt"
)

type Outer struct {
    Inner
    Num int `json:"Num"`
}

type Inner struct{ Data string }

type InnerRaw struct {Data string}

func (i *Inner) UnmarshalJSON(data []byte) error {
    ir:=&InnerRaw{}
    json.Unmarshal(data, ir)
    i.Data = ir.Data
    return nil
}

func main() {
    x := Outer{}
    data := []byte(`{"Num": 4, "Data":"234"}`)
    _ = json.Unmarshal(data, &x)
    fmt.Printf("%+v\n", x)
    js, _:=json.Marshal(x)
    fmt.Printf("JSON:%s", string(js))
}

但是Num仍然是0,尽管它应该是4。 - Taku
嗯,是的,我们可以删除InnerRaw结构体和UnmarshallJSON函数。结果:https://play.golang.org/p/JkKCLQOnsHp,它可以正常工作。 - Mykola M.
OP想要在Inner中有UnmarshalJSON时解析Num。如果我们删除UnmarshalJSON,那么它就无法回答这个问题。 - Taku

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接