When you’re filling a struct by calling :
json.Unmarshal(buff, &yourStruct)
It is not uncommon that your struct contains a non-empty interface.
type IElement interface { GetBody() string } type Message struct { Name string Provider string Elements []IElement }
And of course, json.Unmarshal can’t automatically find matching structs to fill that []IElement. It’s annoying.
Is it really necessary ?
You could make several messages, each one with a specific struct. Then you could Unmarshal them seperatly, and use them as IElement.
type MessageA struct { Name string Provider string Specific []ElementA } type MessageB struct { Name string Provider string Specific []ElementB } // ...
But it’s a pain for API-user, and today, we deal with “developer experience”; we want it as easy as possible.
So, what do we do ?
1. Get the data.
If you unmarshall a json text to this Message struct, Elements will never be filled.
So, let’s change that.
type Message struct { Name string Provider string Elements []map[string]interface{} }
interface{} and map[string]interface{} are the 2 types that json unmarshal can always use to fill data.
Hidden for the API-user, we add the Elements as we want them :
type Message struct { Name string Provider string Elements []map[string]interface{} ElementsBuilded []IElement `json:"-"` // <--- New ! }
In every tag, as in `json:”-“`, the dash means “ignore”.
So ElementsBuilded will never be filled by Unmarshall or read by Marshall
And the trick is to fill ElementsBuilded from Elements.
2. Identify the type
At this step, there is a problem. You don’t know how to recognize the received struct.
type ElementA struct { /* ... */ } type ElementB struct { /* ... */ } type ElementC struct { /* ... */ }
So, let’s add a “Type” field (string) to all elements, used to identify the type of element.
// Declaring properly different type type ElemKind string const ( EElemA ElementKind = "a" // I add prefix "E" to explicit that it's an enum kind of const EElemB ElementKind = "b" EElemC ElementKind = "c" ) // Struct to embed for non-redundant code type headerElement struct { Type string } // The possibles elements type ElementA struct { headerElement } type ElementB struct { headerElement } type ElementC struct { headerElement }
At this point, we add a constraint for the API-user : He must add a type “kind” to all struct. But that’s a tiny cost.
3. From empty interface to non-empty interface
budd := "your json input here" var msg Message err := json.Unmarshal(buff, &msg) if err != nil { panic(err) } for key, elem := range msg.Elements { // First, we get the field "Type" kind, ok := elem["Type"] if !ok { // Deal with error continue } // To Unmarshall to the good struct, we need first // to get the original raw text json of the struct raw, err := json.Marshal(elem) if err != nil { // Should not happen panic(err) } // Now, for each possible type, we Unmarshall to // to the corresponding struct switch kind { case EElemA : var tmp ElementA _ = json.Unmarshal(raw, &tmp) msg.ElementsBuilded = append(msg.ElementsBuilded, tmp) case EElemB : var tmp ElementB _ = json.Unmarshal(raw, &tmp) msg.ElementsBuilded = append(msg.ElementsBuilded, tmp) case EElemC : var tmp ElementC _ = json.Unmarshal(raw, &tmp) msg.ElementsBuilded = append(msg.ElementsBuilded, tmp) } }
Here we are !
Your msg.ElementsBuilded is now ready.