【问题标题】:Golang type conversion/assertion issue with unmarshalling json解组 json 的 Golang 类型转换/断言问题
【发布时间】:2016-04-26 20:02:57
【问题描述】:
package main

import (
    "fmt"
    "encoding/json"
    "reflect"
)

type GeneralConfig map[string]interface{}

var data string = `
{
    "key":"value",
    "important_key":
        {"foo":"bar"}
}`

func main() {
    jsonData := &GeneralConfig{}
    json.Unmarshal([]byte(data), jsonData)

    fmt.Println(reflect.TypeOf(jsonData)) //main.GeneralConfig

    jsonTemp := (*jsonData)["important_key"]
    fmt.Println(reflect.TypeOf(jsonTemp)) //map[string]interface {}

    //newGeneralConfig := GeneralConfig(jsonTemp)
    //cannot convert jsonTemp (type interface {}) to type GeneralConfig:
    //need type assertion

    newGeneralConfig := jsonTemp.(GeneralConfig)
    //fmt.Println(reflect.TypeOf(newGeneralConfig))
    //panic: interface conversion: interface {} is map[string]interface {},
    //not main.GeneralConfig

}

可通过the playground获得

我知道我可以使用嵌套结构代替GeneralConfig,但这需要我知道有效负载的确切结构,即它不适用于不同的键(我会被锁定到“important_key” )。

当我不知道“important_key”的值是什么时,是否有 golang 解决方法?我说 golang,因为如果可能的话,可以要求所有“important_keys”都有一个恒定的父键,这可以解决这个问题。

总而言之,给定一个任意 json 对象,一定有一种方法可以遍历它的键,如果值是自定义类型,则将值转换为该类型。现在看来,如果我使用类型转换,它告诉我类型是interface{},我需要使用类型断言;但是,如果我使用类型断言,它会告诉我 interface{}map[string]interface{} 而不是 main.GeneralConfig

【问题讨论】:

  • 我的建议是弄清楚架构。没有“任意”的 json 这样的东西,我真的厌倦了人们采取低劣的方法,因为他们认为他们的 json 是如此不可预测。我有消息要告诉你,不是!我已经用模式解析了更多变体和复杂的 json,这并不难。 json 是一种形式语言,有一个精确的模式来描述任何 blob,它们可以非常灵活。
  • 当你有一个嵌套的 json 结构并解组它时,你最终会得到一个 map[string]interface{}。 interface{} 部分(如果它是嵌套部分)又是一个 map[string]interface{}。因此,您可以继续向下遍历,直到发现您的 interface{} 不是地图。如果您提供您想要使用的 json 示例,我们可以提出解决方案。
  • @evanmcdonnal 这更像是“理论上,我该怎么做?”出于实际目的,我得出了与您相同的结论。
  • 这很酷。我还处理过类似的困惑,尝试从实际上表示接口切片或映射的接口解包数据。我只是觉得有必要给我的 2 美分,因为正如我所说,我最近在 SO 上看到了很多这样的问题,而且大多数时候作者实际上是在处理非常简单的 json,他们只是在用自己的脚射击这种假设没有一个简单的简明模式来描述它。

标签: go


【解决方案1】:

我同意 cmets 关于尝试利用传入 JSON 的预期结构来编写定义良好的结构,但无论如何我都会尝试回答这个问题。

与您看到的错误消息相比,您看到的打印内容与您看到的错误消息相比,编译器对类型的了解少于运行时,因为运行时可以查看实际值。为了使编译器跟上速度,我们必须 (i) 断言 (*jsonData)["important_key"]map[string]interface{} -- 编译器只知道它是 interface{} -- 然后 (ii) 将其类型转换为 @ 987654324@ 类型。见:

package main

import (
    "fmt"
    "encoding/json"
)

type GeneralConfig map[string]interface{}

func main() {
    jsonStruct := new(GeneralConfig)
    json.Unmarshal([]byte(`{"parent_key": {"foo": "bar"}}`), jsonStruct)
    fmt.Printf("%#v\n", jsonStruct)
    // => &main.GeneralConfig{"parent_key":map[string]interface {}{"foo":"bar"}}

    nestedStruct := (*jsonStruct)["parent_key"]
    fmt.Printf("%#v\n", nestedStruct)
    // => map[string]interface {}{"foo":"bar"}
    // Whilst this shows the runtime knows its actual type is
    // map[string]interface, the compiler only knows it to be an interface{}.

    // First we assert for the compiler that it is indeed a
    // map[string]interface{} we are working with. You can imagine the issues
    // that might arrise if we has passed in `{"parent_key": 123}`.
    mapConfig, ok := nestedStruct.(map[string]interface{})
    if !ok {
        // TODO: Error-handling.
    }

    // Now that the compiler can be sure mapConfig is a map[string]interface{}
    // we can type-cast it to GeneralConfig:
    config := GeneralConfig(mapConfig)
    fmt.Printf("%#v\n", config)
    // => main.GeneralConfig{"foo":"bar"}
}

【讨论】:

  • 感谢您的解释/回答 :)
  • 很高兴我能帮上忙。正如之前在其他地方评论过的,您可能想尝试创建一个类型来表示父容器,例如struct OuterConfig { Config *GeneralConfig `json:"parent_key"` }.
【解决方案2】:

您正在寻找 json.RawMessage。
您可以根据其他值延迟解组,然后强制将其解组为特定类型。

这不是一个好主意,但可能更接近您正在寻找的内容。

http://play.golang.org/p/PWwAUDySE0

【讨论】:

    【解决方案3】:

    如果得到你想要的,这是一个标准的“解决方法”。在处理未知数据时,您可以实现这种模式(根据您的示例修改),以递归方式切换类型以获取未知 json 数据主体中的具体值。

    package main
    
    import (
        "encoding/json"
        "fmt"
        "reflect"
    )
    
    var data = `
    {
        "key":"value",
        "important_key":
            {"foo":"bar"}
    }`
    
    func main() {
        var jsonData interface{}
        json.Unmarshal([]byte(data), &jsonData)
    
        fmt.Println(reflect.TypeOf(jsonData))
    
        parseArbitraryJSON(jsonData.(map[string]interface{}))
    }
    
    func parseArbitraryJSON(data map[string]interface{}) {
        for k, v := range data {
            switch a := v.(type) {
            case string:
                fmt.Printf("%v:%v\n", k, a)
            case map[string]interface{}:
                fmt.Printf("%v:%v\n", k, a)
                parseArbitraryJSON(a)
            }
        }
    }
    

    结果输出是:

    map[string]interface {}
    key:value
    important_key:map[foo:bar]
    foo:bar
    

    此示例仅说明基本数据是字符串类型,但您可以打开您希望接收的任何类型,并且像任何开关一样,您可以对您的案例进行分组,因此您可以类似地处理所有数字。

    【讨论】:

      猜你喜欢
      • 2014-01-08
      • 1970-01-01
      • 2014-01-22
      • 2019-01-01
      • 2019-11-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-11-11
      相关资源
      最近更新 更多