解析具有整数键的嵌套映射

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

Unmarshal nested map with int keys

问题

如果我有一个map,我可以轻松地进行编组:

package main

import (
   "encoding/json"
   "os"
)

type object map[int]interface{}

func main() {
   obj := object{
      1: "one", 2: object{3: "three"},
   }
   buf, err := json.Marshal(obj)
   if err != nil {
      panic(err)
   }
   os.Stdout.Write(buf) // {"1":"one","2":{"3":"three"}}
}

然而,我想要做相反的操作。我尝试了以下代码:

package main

import (
   "encoding/json"
   "fmt"
)

func main() {
   buf := []byte(`{"1":"one","2":{"3":"three"}}`)
   var obj map[int]interface{}
   json.Unmarshal(buf, &obj)
   // map[int]interface {}{1:"one", 2:map[string]interface {}{"3":"three"}}
   fmt.Printf("%#v\n", obj)
}

只有顶层的类型是正确的。我想知道是否有可能实现我想要的效果?

英文:

If I have a map, I can Marshal it no problem:

package main

import (
   "encoding/json"
   "os"
)

type object map[int]interface{}

func main() {
   obj := object{
      1: "one", 2: object{3: "three"},
   }
   buf, err := json.Marshal(obj)
   if err != nil {
      panic(err)
   }
   os.Stdout.Write(buf) // {"1":"one","2":{"3":"three"}}
}

However I want to do the reverse. I tried this:

package main

import (
   "encoding/json"
   "fmt"
)

func main() {
   buf := []byte(`{"1":"one","2":{"3":"three"}}`)
   var obj map[int]interface{}
   json.Unmarshal(buf, &obj)
   // map[int]interface {}{1:"one", 2:map[string]interface {}{"3":"three"}}
   fmt.Printf("%#v\n", obj)
}

Only the top level has the correct type. Is it possible to do what I am wanting?

答案1

得分: 3

JSON键始终只能是字符串,这是规范定义的方式,你可以在你的编组输出中看到这一点。因此,当你尝试使用interface{}作为顶层映射的值类型进行反向操作时,嵌套对象的类型信息就会丢失。你需要一个自定义的映射类型来实现UnmarshalJSON,以便能够实现你想要的功能。

例如:

type IntKeyMap map[int]interface{}

func (m *IntKeyMap) UnmarshalJSON(data []byte) error {
	raw := map[int]json.RawMessage{}
	if err := json.Unmarshal(data, &raw); err != nil {
		return err
	}
	for k, v := range raw {
        // 检查值是否为嵌套对象
		if len(v) > 0 && v[0] == '{' && v[len(v)-1] == '}' {
            // 以下假设嵌套的JSON对象的键字符串表示整数,如果不是这种情况,此块将失败。
			obj := IntKeyMap{}
			if err := json.Unmarshal([]byte(v), &obj); err != nil {
				return err
			}
			(*m)[k] = obj
		} else {
			var i interface{}
			if err := json.Unmarshal([]byte(v), &i); err != nil {
				return err
			}
			(*m)[k] = i
		}
	}
	return nil
}

https://go.dev/play/p/lmyhqD__Uod

英文:

JSON keys are never anything but strings, that's how the spec is defined, and you can see that in the output of you marshal. So when you try the reverse with interface{} as the top level map's value type, the type information for the nested objects is lost. You'd need a custom map type that implements UnmarshalJSON to be able to do what you want.

For example:

type IntKeyMap map[int]interface{}

func (m *IntKeyMap) UnmarshalJSON(data []byte) error {
	raw := map[int]json.RawMessage{}
	if err := json.Unmarshal(data, &raw); err != nil {
		return err
	}
	for k, v := range raw {
        // check if the value is a nested object
		if len(v) > 0 && v[0] == '{' && v[len(v)-1] == '}' {
            // The following assumes that the nested JSON object's
            // key strings represent integers, if that is not the
            // case this block will fail.
			obj := IntKeyMap{}
			if err := json.Unmarshal([]byte(v), &obj); err != nil {
				return err
			}
			(*m)[k] = obj
		} else {
			var i interface{}
			if err := json.Unmarshal([]byte(v), &i); err != nil {
				return err
			}
			(*m)[k] = i
		}
	}
	return nil
}

https://go.dev/play/p/lmyhqD__Uod

huangapple
  • 本文由 发表于 2021年11月25日 11:57:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/70105631.html
匿名

发表评论

匿名网友

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

确定