Unmarshal to custom interface

Issue

The usual approach for unmarshalling is like this:

atmosphereMap := make(map[string]interface{})
err := json.Unmarshal(bytes, &atmosphereMap)

But how to unmarshal json data to custom interface:

type CustomInterface interface {
    G() float64
} 

atmosphereMap := make(map[string]CustomInterface)
err := json.Unmarshal(bytes, &atmosphereMap)

The second way gives me an error:

panic: json: cannot unmarshal object into Go value of type main.CustomInterface

How to do it correctly?

Solution

To unmarshal into a set of types, which all implement a common interface, you can implement the json.Unmarshaler interface on the parent type, the map[string]CustomInterface in your case:

type CustomInterfaceMap map[string]CustomInterface

func (m CustomInterfaceMap) UnmarshalJSON(b []byte) error {
    data := make(map[string]json.RawMessage)
    if err := json.Unmarshal(b, &data); err != nil {
        return err
    }
    for k, v := range data {
        var dst CustomInterface
        // populate dst with an instance of the actual type you want to unmarshal into
        if _, err := strconv.Atoi(string(v)); err == nil {
            dst = &CustomImplementationInt{} // notice the dereference
        } else {
            dst = &CustomImplementationFloat{}
        }

        if err := json.Unmarshal(v, dst); err != nil {
            return err
        }
        m[k] = dst
    }
    return nil
}

For a full example, see this playground. Make sure you unmarshal into a CustomInterfaceMap, not map[string]CustomInterface, otherwise the custom UnmarshalJSON method will not be called.

json.RawMessage is a useful type, which is just a raw encoded JSON value, meaning it is a simple []byte, into which the JSON is stored in unparsed form.

Answered By – Leon

Answer Checked By – Mary Flores (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.