Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

In Go what is the proper way to unmarshall JSON when the fields can have different types depending on other fields?

Assume I have a JSON message that looks like this:

{
    "type": string
    "data": list of something that is based on above type
}

two examples might be

{
    "type": "car"
    "data": [{"color": "red", "mpg": 16.4}]
}

and

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

{
    "type": "house"
    "data": [{"color": "blue", "state": "CA"}]
}

I want to define a struct so I can basically only decode the type and then use that to properly unmarshal the data field. I tried the following

type message struct {
    Type string `json:"type"`
    Data []byte `json:"data"`
}

type car struct {
    Color string
    MPG   float64
}

type house struct {
    Color string
    State string
}

erroneously thinking that it would just leave the Data field raw for me to unmarshal later into either the car or house struct. I know I can define Data as a []interface{} and do some other work to get what I want, but I was wondering if this is (currently) the best way in Go? In case it comes up, assume I cannot change the JSON definitions – I’m just the consumer of a service here.

>Solution :

This is a perfect use-case for json.RawMessage. Check out the Unmarshal example there.

For your example, this would look something like:

type message struct {
    Type string            `json:"type"`
    Data []json.RawMessage `json:"data"`
}

type car struct {
    Color string
    MPG   float64
}

type house struct {
    Color string
    State string
}

func main() {
    if err := parseAndPrint(carJSON); err != nil {
        panic(err)
    }
    if err := parseAndPrint(houseJSON); err != nil {
        panic(err)
    }
}

func parseAndPrint(b []byte) error {
    msg := new(message)
    if err := json.Unmarshal(b, msg); err != nil {
        panic(err)
    }

    switch msg.Type {
    case "car":
        for _, data := range msg.Data {
            c := new(car)
            if err := json.Unmarshal(data, c); err != nil {
                return err
            }
            fmt.Println(c)
        }
    case "house":
        for _, data := range msg.Data {
            h := new(house)
            if err := json.Unmarshal(data, h); err != nil {
                return err
            }
            fmt.Println(h)
        }
    }
    return nil
}


// Tucked here to get out of the way of the example
var carJSON = []byte(`
{
    "type": "car",
    "data": [{"color": "red", "mpg": 16.4}]
}
`)

var houseJSON = []byte(`{
    "type": "house",
    "data": [{"color": "blue", "state": "CA"}]
}
`)

Now what you do with the parsed result is kinda up to you and your program’s needs. Parse at the edge and only pass down the fully-typed message, add a Parsed any field to the outer struct, etc.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading