Reputation: 8807
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?
Upvotes: 2
Views: 1579
Reputation: 12845
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
Upvotes: 0
Reputation: 38203
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.
E.g. 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.
Upvotes: 1
Reputation: 12845
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
Upvotes: 0
Reputation: 11
Change this line:
M map[string]interface{} json:"m"
into :
M interface{} json:"m"
https://play.golang.org/p/qRxo81amA7
Upvotes: 1
Reputation: 28638
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.
Upvotes: 1