Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Polymorphic JSON unmarshalling of embedded structs

Here is an example (see also https://play.golang.org/p/or7z4Xc8tN):

package main

import (
    "encoding/json"
    "fmt"
)

type A struct {
    X string
    Y int
}

type B struct {
    A
    Y string 
}

func main() {
    data := []byte(`{"X": "me", "Y": "hi"}`)
    b := &B{}
    json.Unmarshal(data, b)
    fmt.Println(b)
    fmt.Println(b.A)

    b = &B{}
    data = []byte(`{"X": "me", "Y": 123}`)
    json.Unmarshal(data, b)
    fmt.Println(b)
    fmt.Println(b.A)
}

Which outputs:

&{{me 0} hi}
{me 0}
&{{me 0} }
{me 0}

Is there a way to polymorphically unmarshal the field Y to either an int or a string? Or even unmarshal into A.Y at all since B.Y is defined?

I know some might suggest unmarshalling with something like json.Unmarshall(data, &b.A), but I don't know if I can fit that into my current design.

like image 228
crunk1 Avatar asked Dec 01 '25 11:12

crunk1


2 Answers

Go's only polymorphism is interfaces. Embedding does not offer polymorphism.

If you're trying to unmarshal JSON where you can't assume what type one of the fields is going to be, you can use a field of type interface{} along with type assertions, fmt.Sprint, or reflection. Which you should use depends on the particular use case - once you've got the value, what are you going to do with it? At some point you have to care if it's an int or a string, which will determine how you handle the value.

like image 129
Adrian Avatar answered Dec 03 '25 10:12

Adrian


As pointed by Adrian, Go does not support polymorphism through struct embedding. interface{} is the only way to hold any type of golang variable. However, in your case you can implement custom Unmarshaler to decode the JSON stream to a struct utilizing json.Number or interface{}. Below is implementation using json.Number. For more generic interface{} version, you can implement it as suggested by Adrian.

func (b *B) UnmarshalJSON(js []byte) error {
    //First: decode stream to anonymous struct
    v := struct {
        X string
        Y json.Number
    }{}

    err := json.Unmarshal(js, &v)
    if err != nil {
        return err
    }

    //Depends on the v.Y value, choose the holder variable
    //If it's convertible to number, assign to A.Y
    //otherwise, assign it to b.Y
    b.X = v.X
    if fv, err := v.Y.Float64(); err == nil {
        b.A.Y = int(fv)
    } else {
        b.Y = v.Y.String()
    }

    return nil
}

Working example can be found in The Go Playground.

like image 20
putu Avatar answered Dec 03 '25 08:12

putu



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!