【问题标题】:Parse JSON having sibling dynamic keys alongside with static in Go在 Go 中解析具有兄弟动态键和静态键的 JSON
【发布时间】:2023-03-03 01:58:01
【问题描述】:

我需要解析这个json

{
    "version": "1.1.29-snapshot",
    "linux-amd64": {
        "url": "https://origin/path",
        "size": 7794688,
        "sha256": "14b3c3ad05e3a98d30ee7e774646aec7ffa8825a1f6f4d9c01e08bf2d8a08646"
    },
    "windows-amd64": {
        "url": "https://origin/path",
        "size": 8102400,
        "sha256": "01b8b927388f774bdda4b5394e381beb592d8ef0ceed69324d1d42f6605ab56d"
    }
}

linux-amd64 这样的键是动态的,它们的数量是任意的。我尝试了类似的东西来描述它并解组。显然它不起作用。 Items 始终为空。

type FileInfo struct {
    Url    string `json:"url"`
    Size   int64  `json:"size"`
    Sha256 string `json:"sha256"`
}

type UpdateInfo struct {
    Version string `json:"version"`
    Items   map[string]FileInfo
}

类似于use case,但没有父键items。我想我可以使用 3rd 方库或 map[string]interface{} 方法,但我很想知道如何使用显式声明的类型来实现这一点。

剩下的解析代码是:

func parseUpdateJson(jsonStr []byte) (UpdateInfo, error) {
    var allInfo = UpdateInfo{Items: make(map[string]FileInfo)}
    var err = json.Unmarshal(jsonStr, &allInfo)
    return allInfo, err
}

看看我附上的链接,你会发现这并不像你想象的那么简单。我还指出我对类型化方法感兴趣。好的,如何声明这个map[string]FileInfo被解析?

【问题讨论】:

  • 如果键是动态的,那么您只能使用映射。如果您想在解码 json 时更改结构,有很多使用 json.Unmarshaler 的示例,但仍然必须在内部使用映射。
  • 很明显,因为源json中没有Items字段...
  • @JimB 版本为静态,其余为动态
  • 我创建了一个视频,讨论如何在 Go 中执行混合结构/映射。稍后我也许可以将其变成答案,但目前没有时间。 Watch here
  • N个动态键中的一个是静态的没关系,你仍然需要使用映射。 Items 中没有任何内容,因为 JSON 中没有 "items"

标签: json go dynamic unmarshalling


【解决方案1】:

这个答案改编自我的 YouTube 视频中的 this recipe,关于 Go 中的高级 JSON 处理。

func (u *UpdateInfo) UnmarshalJSON(d []byte) error {
    var x struct {
        UpdateInfo
        UnmarshalJSON struct{}
    }
    if err := json.Unmarshal(d, &x); err != nil {
        return err
    }
    var y map[string]json.RawMessage{}
    if err := json.Unsmarshal(d, &y); err != nil {
        return err
    }


    delete(y, "version"_ // We don't need this in the map
    *u = x.UpdateInfo
    u.Items = make(map[string]FileInfo, len(y))
    for k, v := range y {
        var info FileInfo
        if err := json.Unmarshal(v, &info); err != nil {
            return err
        }
        u.Items[k] = info
    }
    return nil
}

它:

  1. 将 JSON 直接解组到结构中,以获取结构字段。
  2. 它重新解组为map[string]json.RawMessage 的映射以获取任意键。这是必要的,因为version 的值不是FileInfo 类型,因此尝试直接解组到map[string]FileInfo 会出错。
  3. 它会删除我们知道我们已经在结构字段中获得的键。
  4. 然后它遍历stringjson.RawMessage 的映射,最后将每个值解组为FileInfo 类型,并将其存储在最终对象中。

如果您真的不想多次解组,则下一个最佳选择是使用 json.Decoder 类型迭代输入中的 JSON 令牌。我已经在几个对性能敏感的代码中做到了这一点,但这会让你的代码难以阅读,而且几乎在所有情况下都不值得。

【讨论】:

  • 所以没有办法立即避免删除静态键的额外代码,不是吗?
  • 当然,有办法。它要复杂得多,因为它基本上意味着编写自定义 JSON 解组器。除非这段代码对性能非常敏感,否则可能没有理由这样做。
  • @Filmzy 好吧,第 3 方图书馆就是这样。这就是我寻找的答案......
  • @4xy:对不起,什么 3rd 方库?
  • 如果有标准,标准库之外的东西。换句话说,盒子里没有的东西。
【解决方案2】:

您可以创建 json.Unmarshaller 将 json 解码为映射,然后将这些值应用于您的结构:https://play.golang.org/p/j1JXMpc4Q9u

type FileInfo struct {
    Url    string `json:"url"`
    Size   int64  `json:"size"`
    Sha256 string `json:"sha256"`
}

type UpdateInfo struct {
    Version string `json:"version"`
    Items   map[string]FileInfo
}

func (i *UpdateInfo) UnmarshalJSON(d []byte) error {
    tmp := map[string]json.RawMessage{}
    err := json.Unmarshal(d, &tmp)
    if err != nil {
        return err
    }

    err = json.Unmarshal(tmp["version"], &i.Version)
    if err != nil {
        return err
    }
    delete(tmp, "version")

    i.Items = map[string]FileInfo{}

    for k, v := range tmp {
        var item FileInfo
        err := json.Unmarshal(v, &item)
        if err != nil {
            return err
        }

        i.Items[k] = item
    }
    return nil
}

【讨论】:

    猜你喜欢
    • 2015-06-26
    • 2021-05-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-12
    相关资源
    最近更新 更多