解组json的Golang类型转换/断言问题

时间:2016-04-26 20:02:57

标签: go

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

3 个答案:

答案 0 :(得分:2)

我同意关于尝试利用传入JSON的预期结构以便编写定义明确的Structs的评论,但无论如何我都会尝试回答这个问题。

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

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"}
}

答案 1 :(得分:0)

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

这不是一个好主意,但可能更接近您所寻找的目标。

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

答案 2 :(得分:0)

这是标准的"解决方法"如果得到你以后的东西。处理未知数据时,您可以实现此模式(从您的示例中修改),以递归方式切换类型,以获取未知的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

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