Unmarshalling json to structure using json.RawMessage

Issue

I need to unmarshal json object which may have the following formats:

Format1:

{
    "contactType": 2,
    "value": "0123456789"
}

Format2:

{
    "contactType": "MobileNumber",
    "value": "0123456789"
}

The structure I’m using for unmarshalling is:-

type Contact struct {
    ContactType int    `json:"contactType"` 
    Value       string `json:"value"`
}

But this works only for format 1. I don’t want to change the datatype of ContactType but I want to accommodate the 2nd format as well. I heard about json.RawMarshal and tried using it.

type Contact struct {
    ContactType int
    Value       string          `json:"value"`
    Type        json.RawMessage `json:"contactType"`
}

type StringContact struct {
    Type string `json:"contactType"`
}

type IntContact struct {
    Type int `json:"contactType"`
} 

This gets the unmarshalling done, but I’m unable to set the ContactType variable which depends on the type of json.RawMessage. How do I model my structure so that this problem gets solved?

Solution

You will need to do the unmarshalling yourself. There is a very good article that shows how to use the json.RawMessage right and a number of other solutions to this very problem, Like using interfaces, RawMessage, implemention your own unmarshal and decode functions etc.

You will find the article here: JSON decoding in GO by Attila Oláh
Note: Attila has made a few errors on his code examples.

I taken the liberty to put together (using some of the code from Attila) a working example using RawMessage to delay the unmarshaling so we can do it on our own version of the Decode func.

Link to GOLANG Playground

package main

import (
    "fmt"
    "encoding/json"
    "io"
)

type Record struct {
    AuthorRaw json.RawMessage `json:"author"`
    Title     string          `json:"title"`
    URL       string          `json:"url"`

    Author Author
}

type Author struct {
    ID    uint64 `json:"id"`
    Email string `json:"email"`
}

func Decode(r io.Reader) (x *Record, err error) {
    x = new(Record)
    if err = json.NewDecoder(r).Decode(x); err != nil {
        return
    }
    if err = json.Unmarshal(x.AuthorRaw, &x.Author); err == nil {
        return
    }
    var s string
    if err = json.Unmarshal(x.AuthorRaw, &s); err == nil {
        x.Author.Email = s
        return
    }
    var n uint64
    if err = json.Unmarshal(x.AuthorRaw, &n); err == nil {
        x.Author.ID = n
    }
    return
}

func main() {

    byt_1 := []byte(`{"author": 2,"title": "some things","url": "https://stackoverflow.com"}`)

    byt_2 := []byte(`{"author": "Mad Scientist","title": "some things","url": "https://stackoverflow.com"}`)

    var dat Record

    if err := json.Unmarshal(byt_1, &dat); err != nil {
            panic(err)
    }
    fmt.Printf("%#s\r\n", dat)

    if err := json.Unmarshal(byt_2, &dat); err != nil {
            panic(err)
    }
    fmt.Printf("%#s\r\n", dat)
}

Hope this helps.

Answered By – dmportella

Answer Checked By – Senaida (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.