There is no straight forward way to achieve what you want based on the current condition you gave us. @eduncan911 provided a very general method, but however, if you are able to tweak the JSON
input a bit, you are able to achieve it using the following method.
The core idea is to use json.RawMessage
as a buffer to delay the unmarshal until it knows the type it's gonna unmarshal to.
Firstly, tweak the JSON
input to something like below:
{
"Animals": [{
"Type": "dog",
"Property": {
"Name": "Fido"
}
},{
"Type": "fish",
"Property": {
"NumScales": 123
}
}]
}
From what I can see, this tweak does not make the JSON worse, but actually make it better in terms of readability.
Then, create a new struct, say AnimalCard
:
type AnimalCard struct {
Type string
Property json.RawMessage
Animal Animal
}
And modify your Zoo
to
type Zoo struct {
Animals []*AnimalCard
}
Now unmarshal your json to zoo, you will get an array of *AnimalCard
. Now you could iterate through zoo array and unmarshal it according to type:
for _, card := range zoo.Animals {
if card.Type == "dog" {
dog := Dog{}
_ = json.Unmarshal(card.Property, &dog)
card.Animal = dog
} else if card.Type == "fish" {
fish := Fish{}
_ = json.Unmarshal(card.Property, &fish)
card.Animal = fish
}
}
Playground Exmaple is here.
What if I got more and more Animals in the Zoo?
Good question :) The problem the above solution gave won't be that scalable. What if we have 20 animals, not only 2? What if 200? 2000? We need a more general way to do it.
The core idea this time is to use reflect
.
First, we could maintain a map, which maps a type name to an interface implementation:
mapper map[string]Animal{}
Then we put in our animals pointers:
mapper["dog"] = &Dog{}
mapper["fish"] = &Fish{}
Now, after we unmarshalled the JSON into AnimalCard
and start iterating, we use reflection to initialize a new instance pointer and unmarshal into it:
for _, card := range zoo.Animals {
// get the animal type pointer
animal := mapper[card.Type]
// get the pointer's type
animalType := reflect.TypeOf(animal)
// create a new instance pointer of the same type
newInstancePtr := reflect.New(animalType.Elem()).Interface().(Animal)
// unmarshal to the pointer
_ = json.Unmarshal(card.Property, newInstancePtr)
// assign the pointer back
card.Animal = newInstancePtr
}
Playground Example is here.