简体   繁体   English

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

[英]Golang type conversion/assertion issue with unmarshalling json

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

}

Available at the playground 游乐场可用

I understand that I can use a nested struct in lieu of GeneralConfig , but that would require me knowing the exact structure of the payload, ie it wouldn't work for different keys (I would be locked into "important_key"). 我知道我可以使用嵌套结构代替GeneralConfig ,但这需要我知道有效负载的确切结构,即它不适用于不同的键(我将被锁定为“ important_key”)。

Is there a golang workaround for when I don't know what the value of "important_key" is? 当我不知道“ important_key”的值是什么时,是否有golang解决方法? I say golang, because if possible, one could require all "important_keys" to have a constant parent key, which could resolve this issue. 我说golang,因为如果可能的话,可以要求所有“ important_keys”都具有恒定的父键,这可以解决此问题。

To summarize, given an arbitrary json object, there must be a way that I can traverse its keys, and if a value is a custom type, convert the value to that type. 总而言之,对于任意的json对象,必须有一种遍历其键的方法,如果值是自定义类型,则将值转换为该类型。 Right now it seems that if I use type conversion, it tells me that the type is interface{} and I need to use type assertion; 现在看来,如果我使用类型转换,它会告诉我该类型是interface{}并且我需要使用类型断言。 however, if I use type assertion, it tells me that interface{} is map[string]interface{} not main.GeneralConfig . 但是,如果我使用类型断言,它会告诉我interface{}map[string]interface{}而不是main.GeneralConfig

I agree the comments about trying to utilise the expected structure of the incoming JSON in order to write well-defined Structs, but I'll attempt to answer the question anyway. 我同意有关尝试利用传入JSON的预期结构以编写定义良好的Structs的评论,但是无论如何我都会尝试回答这个问题。

The thing to take away from what you're seeing printed versus the error messages that you're seeing is that the compiler knows less about the type than the runtime because the runtime can look at the actual value. 与您看到的打印内容和所看到的错误消息不同的是,编译器对类型的了解要少于运行时,因为运行时可以查看实际值。 To bring the compiler up-to-speed we must (i) assert (*jsonData)["important_key"] is a map[string]interface{} -- the compiler only knows it to be an interface{} -- and then (ii) type-cast that to a GeneralConfig type. 为了使编译器达到最新状态,我们必须(i)断言(*jsonData)["important_key"]map[string]interface{} –编译器只知道它是interface{} –然后(ii)将其类型转换为GeneralConfig类型。 See: 看到:

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

You are looking for json.RawMessage. 您正在寻找json.RawMessage。
You can delay unmarshalling based upon some other value and then force it to unmarshal to a specific type. 您可以根据一些其他值延迟解组,然后强制将其解组为特定类型。

This is not a good idea, but might be closer to what you are looking for. 这不是一个好主意,但可能更接近您要寻找的内容。

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

This is a standard "workaround" if get what you're after. 如果您要得到的是一个标准的“解决方法”。 When handling unknown data you can implement this pattern (modified from your example) of switching on the type recursively to get to the concrete values in an unknown body of json data. 处理未知数据时,您可以实现这种模式(从您的示例中进行修改),以递归方式打开类型以获取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)
        }
    }
}

The resulting output is: 结果输出为:

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

This example only accounts for the base data being a string type but you can switch on any type that you expect to receive, and like any switch you can group your cases, so you can treat all numbers similarly for example. 此示例仅说明基本数据是字符串类型,但是您可以打开希望接收的任何类型,并且像可以对个案进行分组的任何开关一样,例如,可以对所有数字进行类似处理。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM