如何将任意JSON转换为标准字典的golang等效方法?

16

在Python中,你可以做类似这样的事情:

r = requests.get("http://wikidata.org/w/api.php", params=params)
data = r.json()

现在data是一个字典或哈希表(而且,我不需要预先定义字典的结构),我可以通过data ["entities"],data ["entities"] ["Q12"]等来访问键的值。

在golang中该如何实现呢?到目前为止,我有:

resp, err := http.Get("http://wikidata.org/w/api.php?"+v.Encode())
if err != nil {
    // handle error
}

defer resp.Body.Close()
decoder := json.NewDecoder(resp.Body)
var data interface{}
decodeErr := decoder.Decode(&data)
if decodeErr != nil {
    // handle error
}
fmt.Println(data["entities"], data["entities"]["Q"+id])

这使我编译时遇到错误:invalid operation: data["entities"] (index of type interface {})

那么var data应该是什么类型呢?我需要预先定义JSON的结构,还是可以处理任何JSON文件/流而不修改代码?

2个回答

34
如果你需要一个字典,可以使用Go类型map[string]interface{}(这是一个键为string,值为任何类型的map):
var data map[string]interface{}

然后你可以像这样引用它的元素:

data["entities"]

看这个例子:

s := `{"text":"I'm a text.","number":1234,"floats":[1.1,2.2,3.3],
    "innermap":{"foo":1,"bar":2}}`

var data map[string]interface{}
err := json.Unmarshal([]byte(s), &data)
if err != nil {
    panic(err)
}

fmt.Println("text =", data["text"])
fmt.Println("number =", data["number"])
fmt.Println("floats =", data["floats"])
fmt.Println("innermap =", data["innermap"])

innermap, ok := data["innermap"].(map[string]interface{})
if !ok {
    panic("inner map is not a map!")
}
fmt.Println("innermap.foo =", innermap["foo"])
fmt.Println("innermap.bar =", innermap["bar"])

fmt.Println("The whole map:", data)

输出:

text = I'm a text.
number = 1234
floats = [1.1 2.2 3.3]
innermap = map[foo:1 bar:2]
innermap.foo = 1
innermap.bar = 2
The whole map: map[text:I'm a text. number:1234 floats:[1.1 2.2 3.3]
    innermap:map[foo:1 bar:2]]

你可以在Go Playground上尝试一下。

注:

基本上,如果你的map是多层级的(即map包含另一个map),比如上面例子中的"innermap",当你访问内部map时,你可以使用类型断言将其作为另一个map来处理:

innermap, ok := data["innermap"].(map[string]interface{})
// If ok, innermap is of type map[string]interface{}
// and you can refer to its elements.

我需要一个字典,但它必须支持多级层次或父子关系。目前只支持一级,例如 {"key1": "val1", "key2": "val2"},但我还需要支持任意数量的级别,例如 {"key1": {"c1key1": "c1val1"}}{"key1": {"c1key1": {"c2key1: "c2val1"}}} - 01AutoMonkey
@01AutoMonkey 这个确实支持多层嵌套的地图,深度可以无限制。我已经编辑了我的答案,包含一个内部地图。 - icza

3

我更喜欢添加类型声明,这样你就可以添加方法来简化类型断言:

package main

type mapType map[string]interface{}

func (t mapType) m(s string) mapType {
   return t[s].(map[string]interface{})
}

func (t mapType) f(s string) float64 {
   return t[s].(float64)
}

例子:

package main

import (
   "encoding/json"
   "net/http"
)

func main() {
   req, err := http.NewRequest("GET", "http://wikidata.org/w/api.php", nil)
   if err != nil {
      panic(err)
   }
   q := req.URL.Query()
   q.Set("action", "wbgetentities")
   q.Set("format", "json")
   q.Set("ids", "Q24871")
   req.URL.RawQuery = q.Encode()
   res, err := new(http.Client).Do(req)
   if err != nil {
      panic(err)
   }
   defer res.Body.Close()
   var t mapType
   json.NewDecoder(res.Body).Decode(&t)
   println(t.m("entities").m("Q24871").f("pageid") == 28268)
}

https://golang.org/ref/spec#Type_declarations


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接