Unmarshaling in interface {} and then executing a statement like

I get string through rabbitmq message system. Before sending,

I use json.Marshal , convert the result to string and send via RabbitMQ.

The structures that I convert and submit can be: (the names and sizes of the structures are changed, but it does 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 fine as a string and prints on the other side (on some server)

Everything is still working. Now I am trying to convert them to my structures and assert types.

first try:

 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 I type input after canceling the cancellation, I get map[string]interface{} (?!?)

and even stranger, the card key is the string I received, and the card value is empty.

I made several other attempts:

  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 to write a switch, as explained in this answer: Golang: cannot enter a key on a value without an interface

 switch v := interface{}(input).(type) 

still without success ...

Any ideas?

+6
source share
2 answers

The default types that json package Unmarshals are placed in are shown in the Unmarshal 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 are unmounting in interface{} , the return types will only be from this set. The json package does not know about Something1 and Something2 . You need to either convert from map[string]interface{} that the json object will not be marshaled, or unmount directly to the type of structure you want.

If you donโ€™t want to unpack the data from the common interface or somehow mark the data so that you know what type to expect, you can iteratively take json and try to decouple it into every type you want.

You can even pack them into a wrapper structure to do the markup 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 } 

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

+9
source

You are facing a typical json vs typed language! Since json is untyped and sketchy, it is impossible to infer what data is โ€œbelow the lineโ€ without actual decoding.

So your only option is to unmount into interface{} , which always creates map[string]interface{} . You can do reflection magic here to build the final structure, but it is a lot of manual work and error prone. Here are some possible solutions:

Fast n dirty

Let the json package do reflection material. Try to untie each 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 } 

Create your own type system on top of json

Set aside the encoding until you know what's inside. Use this structure 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) 

unpack:

 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 typed serialization format

+7
source

All Articles