Golang在解析JSON时的类型转换/断言问题

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

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
	
}

这段代码的目标是将一个 JSON 对象转换为 GeneralConfig 类型的变量。但是,由于不知道 "important_key" 的值是什么,所以无法进行类型转换。

你是否有任何关于如何解决这个问题的想法?

英文:
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").

Is there a golang workaround for when I don't know what the value of "important_key" is? I say golang, because if possible, one could require all "important_keys" to have a constant parent key, which could resolve this issue.

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. 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; however, if I use type assertion, it tells me that interface{} is map[string]interface{} not main.GeneralConfig.

答案1

得分: 2

我同意关于尝试利用传入JSON的预期结构以编写明确定义的结构体的评论,但我将尝试回答这个问题。

从你打印出的内容和你看到的错误消息中可以得出的结论是,编译器对类型的了解比运行时少,因为运行时可以查看实际值。为了让编译器了解情况,我们必须(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"}
	// 尽管这显示运行时知道其实际类型是map[string]interface{},但编译器只知道它是一个interface{}类型。

	// 首先,我们断言编译器确实是在处理map[string]interface{}。你可以想象如果我们传入`{"parent_key": 123}`会出现什么问题。
	mapConfig, ok := nestedStruct.(map[string]interface{})
	if !ok {
		// TODO: 错误处理。
	}

	// 现在编译器可以确定mapConfig是一个map[string]interface{}类型,我们可以将其类型转换为GeneralConfig:
	config := GeneralConfig(mapConfig)
	fmt.Printf("%#v\n", config)
	// => main.GeneralConfig{"foo":"bar"}
}

希望对你有帮助!

英文:

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.

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

答案2

得分: 0

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

这不是一个好主意,但可能更接近你要找的东西。

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

英文:

You are looking for 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

答案3

得分: 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

这个示例只考虑了基本数据类型为字符串的情况,但你可以根据你预期接收到的任何类型进行切换,并且像任何切换语句一样,你可以将多个 case 分组,以便对所有数字进行类似处理。

英文:

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.

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.

huangapple
  • 本文由 发表于 2016年4月27日 04:02:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/36874689.html
匿名

发表评论

匿名网友

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

确定