在Go中解析同时具有静态和动态兄弟键的JSON

huangapple go评论82阅读模式
英文:

Parse JSON having sibling dynamic keys alongside with static in Go

问题

我需要解析这个 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
}

这类似于这个案例,但没有父键 items。我想我可以使用第三方库或 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 来进行解析呢?

英文:

I need to parse this 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"
	}
}

Keys like linux-amd64 are dynamic and theirs amount is arbitrary. I tried something like that to describe it and unmarshal. Obviously it doesn't work. Items is always empty.

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
}

It's similar to this use case, but has no parent key items. I suppose I can use 3rd party library or map[string]interface{} approach, but I'm interested in knowing how to achieve this with explicitly declared types.

The rest of the parsing code is:

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

Look at the link I attached and you will realize that is not that simple as you think. Also I pointed that I interested in typed approach. Ok, how to declare this map[string]FileInfo to get parsed?

答案1

得分: 4

你可以创建一个json.Unmarshaller来将JSON解码为一个map,然后将这些值应用到你的结构体中。以下是示例代码:

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
}

你可以在这里查看完整的示例代码:https://play.golang.org/p/j1JXMpc4Q9u

英文:

You can create a json.Unmarshaller to decode the json into a map, then apply those values to your struct: 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
}

答案2

得分: 1

这个答案是根据我在YouTube视频中关于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") // 我们不需要这个键
    *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类型,并将其存储在最终对象中。

<hr>

如果你真的不想多次解组,你下一个最好的选择是使用json.Decoder类型迭代输入中的JSON标记。我在一些性能敏感的代码中使用过这种方法,但它会使你的代码非常难以阅读,并且在几乎所有情况下都不值得这样做的努力。

英文:

This answer is adapted from this recipe in my YouTube video on advanced JSON handling in Go.

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


    delete(y, &quot;version&quot;_ // We don&#39;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, &amp;info); err != nil {
            return err
        }
        u.Items[k] = info
    }
    return nil
}

It:

  1. Unmarshals the JSON into the struct directly, to get the struct fields.
  2. It re-unmarshals into a map of map[string]json.RawMessage to get the arbitrary keys. This is necessary since the value of version is not of type FileInfo, and trying to unmarshal directly into map[string]FileInfo will thus error.
  3. It deletes the keys we know we already got in the struct fields.
  4. It then iterates through the map of string to json.RawMessage, and finally unmarshals each value into the FileInfo type, and stores it in the final object.

<hr>

If you really don't want to unmarshal multiple times, your next best option is to iterate over the JSON tokens in your input by using the json.Decoder type. I've done this in a couple of performance-sensitive bits of code, but it makes your code INCREDIBLY hard to read, and in almost all cases is not worth the effort.

huangapple
  • 本文由 发表于 2021年7月14日 00:59:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/68366585.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定