使用不同的键进行 JSON 解组

psw*_*han 5 json struct go

我正在尝试从可能具有不同密钥的不同来源解组一些 JSON。例如,我可能有:

{
    "a": 1,
    "b": 2
}
Run Code Online (Sandbox Code Playgroud)

或者我可能有:

{
    "c": 1,
    "b": 2
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我可以保证“b”会在那里。但是,我希望“a”和“c”以相同的方式表示。实际上,我想要的是:

type MyJson struct {
    Init int `json:"a",json:"c"`
    Sec  int `json:"b"
}
Run Code Online (Sandbox Code Playgroud)

基本上我希望解组器查找任一键并将其设置为Init. 现在这实际上不起作用(或者我不会发布)。解组第一个给了我我想要的,而第二个设置Init为 0。我理想的选择是解组到一个结构体,有两种可能性之一:

  1. 我用上面的多个标签表示结构
  2. 我定义了多个结构,但可以选择要解组的结构

我尝试通过创建两个不同的结构和一个映射来实现数字 2,但似乎我无法创建一个类型为第二个值的映射:

type MyJson1 struct {
    Init int `json:"a"`
    Sec  int `json:"b"`
}

type MyJson2 struct {
    Init int `json:"c"`
    Sec  int `json:"b"`
}
Run Code Online (Sandbox Code Playgroud)

有没有办法定义一组行为类似于接口的结构?也就是说,它们都具有相同的字段,但定义不同。或者也许还有另一种方法。我可以使这些字段解组“a”和“c”其他字段,然后进行Init相应的设置。但这不会超出几个变体。

谢谢!

Jae*_*ark 5

icza 的方法很好,如果这个实现能够匹配 stdlib 的接口就更好了。

我建议实施json.Unmarshaler

// it can't get c itself.
type MyJson struct {
    A int `json:"a"`
    B int `json:"b"`
}

func (j *MyJson) UnmarshalJSON(b []byte) error {
    type Alias MyJson
    realValue := struct {
        *Alias
        C int `json:"c"`  // <- now it can accept 'c' value
    }{(*Alias)(j), 0}

    if err := json.Unmarshal(b, &realValue); err != nil {
        return err
    } else if realValue.C != 0 {
        // if C has value, overwrite A
        realValue.A = realValue.C
    }

    return nil
}
Run Code Online (Sandbox Code Playgroud)

只需使用 对其进行解码即可json.Unmarshal


icz*_*cza 4

一种可能性是定义一个struct包含可能输入的所有变体的字段,并为方便起见,为此结构提供一个方法,该方法将返回在输入中找到的字段:

type MyJson struct {
    A *int `json:"a"`
    C *int `json:"c"`

    Sec int `json:"b"`
}

func (j *MyJson) Init() int {
    if j.A == nil {
        return *j.C
    }
    return *j.A
}
Run Code Online (Sandbox Code Playgroud)

使用它:

inputs := []string{
    `{"a": 1, "b": 2}`,
    `{"c": 1, "b": 2}`}

for _, input := range inputs {
    var my MyJson
    if err := json.Unmarshal([]byte(input), &my); err != nil {
        panic(err)
    }
    fmt.Printf("Init: %v, Sec: %v\n", my.Init(), my.Sec)
}
Run Code Online (Sandbox Code Playgroud)

输出,如预期(在Go Playground上尝试):

Init: 1, Sec: 2
Init: 1, Sec: 2
Run Code Online (Sandbox Code Playgroud)

还有一个小技巧:

在原始结构中,我们为 2 个可能的变体添加了 2 个字段。我将它们定义为指针,以便我们可以检测在 JSON 输入中找到了哪一个。现在,如果在解组之前我们将这些指针设置为指向相同的值,这就是我们所需要的:无论我们使用输入 JSON 的哪种变体,都会在内存中设置相同的值,因此您始终可以读取/引用结构体Init字段:

type MyJson struct {
    Init *int `json:"a"`
    Init2 *int `json:"c"`

    Sec int `json:"b"`
}

func main() {
    inputs := []string{
        `{"a": 1, "b": 2}`,
        `{"c": 1, "b": 2}`}

    for _, input := range inputs {
        var my MyJson
        my.Init = new(int) // Allocate an int
        my.Init2 = my.Init // Set Init2 to point to the same value

        if err := json.Unmarshal([]byte(input), &my); err != nil {
            panic(err)
        }
        fmt.Printf("Init: %v, Sec: %v\n", *my.Init, my.Sec)
    }
}
Run Code Online (Sandbox Code Playgroud)

在Go Playground上尝试一下。

您可以创建一个函数来创建并设置您的MyJson解组准备,如下所示:

func NewMyJson() (my MyJson) {
    my.Init = new(int) // Allocate an int
    my.Init2 = my.Init // Set Init2 to point to the same value
    return
}
Run Code Online (Sandbox Code Playgroud)

因此使用它变得如此简单:

var my = NewMyJson()
err := json.Unmarshal([]byte(input), &my)
Run Code Online (Sandbox Code Playgroud)

Tricked 变体的另一个技巧:

您可以指定该Init字段不是指针,因为作为Init2指针并指向就足够了Init,因此这变得更加简单和理想(但NewMyJson必须返回一个指针):

type MyJson struct {
    Init  int `json:"a"`
    Init2 *int `json:"c"`

    Sec int `json:"b"`
}

func NewMyJson() *MyJson {
    my := new(MyJson)
    my.Init2 = &my.Init // Set Init2 to point to Init
    return my
}
Run Code Online (Sandbox Code Playgroud)