英文:
Unmarshaling Into an Interface{} and Then Performing Type Assertion
问题
我通过rabbitmq消息系统获取一个string
。在发送之前,我使用json.Marshal
将结果转换为string
,然后通过rabbitmq发送。
我要转换和发送的结构体可以是以下之一:(更改了结构体的名称和大小,但不应该影响)
type Something1 struct {
Thing string `json:"thing"`
OtherThing int64 `json:"other_thing"`
}
type Something2 struct {
Croc int `json:"croc"`
Odile bool `json:"odile"`
}
消息作为string
完美地传递,并在另一端(某个服务器)打印出来。
到目前为止,一切都正常。现在我想将它们转换回它们的结构体并断言类型。
第一次尝试是:
func typeAssert(msg string) {
var input interface{}
json.Unmarshal([]byte(msg), &input)
switch input.(type) {
case Something1:
job := Something1{}
job = input.(Something1)
queueResults(job)
case Something2:
stats := Something2{}
stats = input.(Something2)
queueStatsRes(stats)
default:
}
}
这不起作用。在解组后打印input
的类型时,我得到了map[string]interface{}
(?!)
更奇怪的是,映射键是我得到的字符串,而映射值为空。
我尝试了其他方法,例如:
func typeAssert(msg string) {
var input interface{}
json.Unmarshal([]byte(msg), &input)
switch v := input.(type) {
case Something1:
v = input.(Something1)
queueResults(v)
case Something2:
v = input.(Something2)
queueStatsRes(v)
default:
}
}
我还尝试按照这个答案中所解释的方式编写switch语句:
Golang: cannot type switch on non-interface value
switch v := interface{}(input).(type) {
但仍然没有成功...
有什么想法吗?
英文:
I get a string
through a rabbitmq message system. Before sending,
I use json.Marshal
, convert the outcome to string
and send through
rabbitmq.
The structs that I convert and send can be: (changed the names and the size of the structs but it should not matter)
type Somthing1 struct{
Thing string `json:"thing"`
OtherThing int64 `json:"other_thing"`
}
or
type Somthing2 struct{
Croc int `json:"croc"`
Odile bool `json:"odile"`
}
The message goes through perfectly as a string
and is printed
on the other side (some server)
Up until now everything works.
Now I'm trying to convert them back into their structs and assert the types.
first attempt is by:
func typeAssert(msg string) {
var input interface{}
json.Unmarshal([]byte(msg), &input)
switch input.(type){
case Somthing1:
job := Somthing1{}
job = input.(Somthing1)
queueResults(job)
case Somthing2:
stats := Somthing2{}
stats = input.(Somthing2)
queueStatsRes(stats)
default:
}
This does not work. When Printing the type of input
after Unmarshaling
it I get map[string]interface{}
(?!?)
and even stranger than that, the map key is the string I got and the map value is empty.
I did some other attempts like:
func typeAssert(msg string) {
var input interface{}
json.Unmarshal([]byte(msg), &input)
switch v := input.(type){
case Somthing1:
v = input.(Somthing1)
queueResults(v)
case Somthing2:
v = input.(Somthing2)
queueStatsRes(v)
default:
}
and also tried writing the switch like was explained in this answer:
Golang: cannot type switch on non-interface value
switch v := interface{}(input).(type)
still with no success...
Any ideas?
答案1
得分: 45
json
包在解组时默认的类型如下所示:
bool
,用于JSON布尔值float64
,用于JSON数字string
,用于JSON字符串[]interface{}
,用于JSON数组map[string]interface{}
,用于JSON对象nil
,用于JSON null
由于您正在解组到interface{}
类型中,返回的类型将仅限于该集合。json
包不知道Something1
和Something2
。您需要将其从解组为map[string]interface{}
的对象进行转换,或直接解组为所需的结构类型。
如果您不想从通用接口中解包数据,或者以某种方式标记数据以了解预期的类型,您可以迭代地将JSON数据尝试解组为每个所需的类型。
您甚至可以将它们打包到一个包装结构中,以便为您执行解组操作:
type Something1 struct {
Thing string `json:"thing"`
OtherThing int64 `json:"other_thing"`
}
type Something2 struct {
Croc int `json:"croc"`
Odile bool `json:"odile"`
}
type Unpacker struct {
Data interface{}
}
func (u *Unpacker) UnmarshalJSON(b []byte) error {
smth1 := &Something1{}
err := json.Unmarshal(b, smth1)
// 没有错误,但我们还需要确保解组了某些内容
if err == nil && smth1.Thing != "" {
u.Data = smth1
return nil
}
// 如果有错误,但错误类型不是类型错误,则中止
if _, ok := err.(*json.UnmarshalTypeError); err != nil && !ok {
return err
}
smth2 := &Something2{}
err = json.Unmarshal(b, smth2)
if err != nil {
return err
}
u.Data = smth2
return nil
}
您可以在此处查看示例代码:http://play.golang.org/p/Trwd6IShDW
英文:
The default types that the json
package Unmarshals into are shown in the Unmarshal
function documentation
bool, for JSON booleans
float64, for JSON numbers
string, for JSON strings
[]interface{}, for JSON arrays
map[string]interface{}, for JSON objects
nil for JSON null
Since you're unmarshaling into an interface{}
, the returned types will only be from that set. The json
package doesn't know about Something1
and Something2
. You either need to convert from the map[string]interface{}
that the json object is being unmarshaled into, or unmarshal directly into the struct type you want.
If you don't want to do unpack the data from a generic interface, or somehow tag the data so you know what type to expect, you could iteratively take the json and try to unmarshal it into each type you want.
You can even pack those into a wrapper struct to do the unmarshaling for you:
type Something1 struct {
Thing string `json:"thing"`
OtherThing int64 `json:"other_thing"`
}
type Something2 struct {
Croc int `json:"croc"`
Odile bool `json:"odile"`
}
type Unpacker struct {
Data interface{}
}
func (u *Unpacker) UnmarshalJSON(b []byte) error {
smth1 := &Something1{}
err := json.Unmarshal(b, smth1)
// no error, but we also need to make sure we unmarshaled something
if err == nil && smth1.Thing != "" {
u.Data = smth1
return nil
}
// abort if we have an error other than the wrong type
if _, ok := err.(*json.UnmarshalTypeError); err != nil && !ok {
return err
}
smth2 := &Something2{}
err = json.Unmarshal(b, smth2)
if err != nil {
return err
}
u.Data = smth2
return nil
}
答案2
得分: 27
你遇到了典型的 JSON 与强类型语言之间的问题!由于 JSON 是无类型且无模式的,无法在不解码的情况下推断出字符串下面的数据是什么。
因此,你唯一的选择是将其解组为 interface{}
,这将始终生成一个 map[string]interface{}
。你可以在这里使用一些反射技巧来构建最终的结构体,但这需要大量的手动工作且容易出错。
以下是一些可能的解决方案:
快速而简单的方法
让 json
包处理反射工作。尝试将数据解组为每种预期的类型:
func typeAssert(msg string) {
var thing1 Something1
err := json.Unmarshal([]byte(msg), &thing1)
if err == nil{
// 处理 thing1
return
}
var thing2 Something2
err = json.Unmarshal([]byte(msg), &thing2)
if err == nil{
// 处理 thing2
return
}
// 处理不支持的类型
}
在 JSON 上构建自己的“类型系统”
推迟编码,直到你知道其中的内容。使用以下结构体作为数据的中间表示:
type TypedJson struct{
Type string
Data json.RawMessage
}
编码:
thing := Something1{"asd", 123}
tempJson, _ := json.Marshal(thing)
typedThing := TypedJson{"something1", tempJson}
finalJson, _ := json.Marshal(typedThing)
解码:
func typeAssert(msg string) {
var input TypedJson
json.Unmarshal([]byte(msg), &input)
switch input.Type{
case "something1":
var thing Something1
json.Unmarshal(input.Data, &thing)
queueStatsRes(thing)
case "something2":
var thing Something2
json.Unmarshal(input.Data, &thing)
queueStatsRes(thing)
default:
// 处理不支持的类型
}
使用强类型的序列化格式
-
Go 自带的 gob 编码
-
还有许多其他选项...
英文:
You have encountered a typical json vs typed language problem!
Since json is untyped and schemaless, it is not possible to infer what data is "under the string" without actually decoding it.
So your only option is to unmarshal into an interface{}
which always produces a map[string]interface{}
. You could do some reflection magic here to build the final struct, but that's a lot of manual work and error prone.
Here are some possible solutions:
Quick 'n' dirty
Let the json
package do the reflection stuff. Attempt to unmarshal into every expected type:
func typeAssert(msg string) {
var thing1 Something1
err := json.Unmarshal([]byte(msg), &thing1)
if err == nil{
// do something with thing1
return
}
var thing2 Something2
err = json.Unmarshal([]byte(msg), &thing2)
if err == nil{
// do something with thing2
return
}
//handle unsupported type
}
Build your own "type system" on top of json
Defer the encoding until you know what's inside. Use this struct as an intermediate representation of your data:
type TypedJson struct{
Type string
Data json.RawMessage
}
Marshal:
thing := Something1{"asd",123}
tempJson, _ := json.Marshal(thing)
typedThing := TypedJson{"something1", tempJson}
finalJson, _ := json.Marshal(typedThing)
Unmarshal:
func typeAssert(msg string) {
var input TypedJson
json.Unmarshal([]byte(msg), &input)
switch input.Type{
case "something1":
var thing Something1
json.Unmarshal(input.Data, &thing)
queueStatsRes(thing)
case "something2":
var thing Something2
json.Unmarshal(input.Data, &thing)
queueStatsRes(thing)
default:
//handle unsupported type
}
Use a typed serialization format
- Go's own gob encoding
- Protocol Buffers
- and many more...
答案3
得分: 0
我喜欢这种“Message”类型的风格,它可以解析任何预期的消息。
一些好处:
- 它在更大的JSON结构中作为子类型时表现良好,因为它实现了
UnmarshalJSON
。这使得它更具可重用性。 - 消息始终解析为相同的类型,因此我们是静态类型的,但类型字段告诉您它是什么类型的消息。
- Something1/Something2字段是强类型的,但只有正确的字段被填充。
我希望顶层消息有一个明确的类型。类似于{"messageType":"Something1", "messageData":{...}}
这样的形式。这将消除解析过程中的试错因素。但是您并不总是能够控制数据源,可能需要采用其他方法。
type Something1 struct{
Thing string `json:"thing"`
OtherThing int64 `json:"other_thing"`
}
type Something2 struct{
Croc int `json:"croc"`
Odile bool `json:"odile"`
}
type Message struct{
Type string // 这里可以使用枚举类型,但为了简洁起见,使用字符串
// 使用指针,因为只有其中一个会被填充,另一个将为nil。可以根据需要添加任意数量的消息类型。
Something1 *Something1
Something2 *Something2
}
func (m *Message) UnmarshalJSON(b []byte) error {
var s1 Something1
err := json.Unmarshal(b, &s1)
if err == nil {
// 这一行是某种检查,用于验证s1是否为有效的Something1。具体取决于用例/数据模型。
if s1.Thing != "" {
m.Type = "Something1"
m.Something1 = &s1
return nil
}
}
var s2 Something2
err = json.Unmarshal(b, &s2)
if err == nil {
// 这一行是某种检查,用于验证s2是否为有效的Something2。具体取决于用例/数据模型。
if s2.Croc > 0 {
m.Type = "Something2"
m.Something2 = &s2
return nil
}
}
return errors.New("无效的消息")
}
示例(基于JimB的示例):https://go.dev/play/p/vQfY--lSGmh
英文:
I like this style of a "Message" type which can unmarshal any expected message.
Few benefits:
- It plays nice with being a subtype in a bigger json structure because it implements
UnmarshalJSON
. This makes it more reusable. - Message is always parsed to the same type so we're statically typed, but a type field tells you what type of message it is
- The Somthing1/Somthing2 fields are strongly typed, but only the correct one is populated.
I would prefer if the top level message had an explicit type. Something like {"messageType":"Somthing1", "messageData":{...}}
. That would take the trial and error aspect out of parsing. But you can't always control the data source and may need to resort to it.
type Somthing1 struct{
Thing string `json:"thing"`
OtherThing int64 `json:"other_thing"`
}
type Somthing2 struct{
Croc int `json:"croc"`
Odile bool `json:"odile"`
}
type Message struct{
Type string // enum type here would be nice, but string for brevity
// pointers, because only one of these will be populated, and the other will be nil. Can add as many as you want as you add message types.
Somthing1 *Somthing1
Somthing2 *Somthing2
}
func (m *Message) UnmarshalJSON(b []byte) error {
var s1 Somthing1
err := json.Unmarshal(b, &s1)
if err == nil {
// this line is some sort of check s1 is a valid Somthing1. Will depend on use case/data model
if s1.Thing != "" {
m.Type = "Somthing1"
m.Somthing1 = &s1
return nil
}
}
var s2 Somthing2
err = json.Unmarshal(b, &s2)
if err == nil {
// this line is some sort of check s2 is a valid Somthing2. Will depend on use case/data model
if s2.Croc > 0 {
m.Type = "Somthing2"
m.Somthing2 = &s2
return nil
}
}
return errors.New("Invalid message")
}
Example (based off of JimB's sample): https://go.dev/play/p/vQfY--lSGmh
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论