使用不同的键进行 JSON 解码

标签 json struct go

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

{
    "a": 1,
    "b": 2
}

或者我可能有:

{
    "c": 1,
    "b": 2
}

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

type MyJson struct {
    Init int `json:"a",json:"c"`
    Sec  int `json:"b"
}

基本上我希望解码器查找任一键并将其设置为 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"`
}

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

谢谢!

最佳答案

一种可能性是定义一个 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
}

使用它:

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)
}

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

Init: 1, Sec: 2
Init: 1, Sec: 2

还有一个小技巧:

在原始结构中,我们为 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)
    }
}

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
}

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

var my = NewMyJson()
err := json.Unmarshal([]byte(input), &my)

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
}

关于使用不同的键进行 JSON 解码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28711882/

相关文章:

go - htop 和 golang readmemstats 之间的差异

ios - NSJSONSerialization 使用有效的 json 返回 nil

javascript - 根据不匹配某些值返回 JSON

javascript - 使用 AJAX 和 JavaScript 从 API 访问 JSON 对象

c - 如何实现对结构的各个字段执行相同操作的函数?

go - Go 中方法的作用域是什么?

java - 为什么我的 JSON 响应前面带有 {} &&

c - 将文本文件读入结构指针时出现段错误

c++ - 关于使用读写在文件中存储结构

go - 如何在单个程序中使用多个 sync.WaitGroup