简体   繁体   中英

Deserialize into map[string]interface{} as a concrete map type

type Foo struct {
    M map[string]interface{} `json:"m"`
}

type Bar struct {
    I int `json:"i"`
}

type Bar2 struct {
    S string `json:"s"`
}


func do() {
    concreteFoo1 := Foo {
        M: make(map[string]Bar),
    }
    json.Unmarshal([]byte(`{"m": {"a": { "i": 1 }}}`), &concreteFoo1)

    concreteFoo2 := Foo {
        M: make(map[string]Bar2),
    }

    json.Unmarshal([]byte(`{"m": {"a": { "s": "hello" }}}`), &concreteFoo2)

}

fails to compile with:

cannot use make(map[string]Bar) (type map[string]Bar) as type map[string]interface {} in field value

cannot use make(map[string]Bar2) (type map[string]Bar2) as type map[string]interface {} in field value

How can I get this to compile, and support both variants of Foo?

Change Foo.m to be map[string]Bar instead of what you have, that will allow it to compile. To make it work, you need to change Foo.m to Foo.M and Bar.i to Bar.I . The Go JSON library will not unmarshal to or marshal from unexported properties. Mapping the uppercase properties to lowercase JSON elements requires using tags. Full working example here .

Change this line: M map[string]interface{} json:"m"

into : M interface{} json:"m"

https://play.golang.org/p/qRxo81amA7

If the set of possible Bar types is infinite you should use the solution from Stud's answer . After unmarshaling you just need to do one type assertion to get the map , which is much better then doing type assertion on each value of a map[string]interface{} type.

If, however, the set is limited to only a few types you could create a parent Bar type that embeds the list of all the possible Bar variations. In case there's a possibility that the fields of the embedded types would collide you can still have the embedding type implement the json.Unmarshaler interface and do some custom logic on what to unmarshal where.

Eg something like this:

type BarSet struct {
    *Bar
    *Bar2
}

type Bar struct {
    I int `json:"i"`
}

type Bar2 struct {
    S string `json:"s"`
}

https://play.golang.org/p/tJfqtnP-CX

And if you want to be as strict as possible you should create separate Foo types, each with their distincitve M map[string]... field.

You can make generalized interface

type Foo struct {
    M map[string]Ibar `json:"m"`
}

type Ibar interface{}

And unpack into it.

When you need specific value of variable make type assertion.

https://play.golang.org/p/fcs-Yp-ck2

I've looked again and think custom unmarshaler can be good option.

func (f *Foo) UnmarshalJSON(b []byte) error {
    var tmp struct {
        M map[string]json.RawMessage `json:"m"`
    }
    var foo = Foo{
        M: make(map[string]interface{}),
    }
    var err error
    //for key, rawValue := range
    err = json.Unmarshal(b, &tmp)
    if err != nil {
        return err
    }
    for key, rawValue := range tmp.M {
        var bar Bar
        if json.Unmarshal(rawValue, &bar) == nil && bar.I != 0 { // custom check
            foo.M[key] = bar
            continue
        }
        var bar2 Bar2
        if json.Unmarshal(rawValue, &bar2) == nil && bar2.S != "" { // custom check
            foo.M[key] = bar2
            continue
        }
    }
    *f = foo
    return nil
}

Advantages:

  • Logics in one place

  • Standard call UnmarshalJSON

  • Can handle several subobjects

https://play.golang.org/p/ug9rewqjWw

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM