我有一个接口,声明一个方法和一些实现该接口的结构。现在我想将一些JSON解组为这些结构的实例。即:
package main
import (
"encoding/json"
"fmt"
)
type Animal interface {
makeNoise() string
}
type Dog struct {
Name string
}
func (d Dog) makeNoise() string {
return "woof"
}
type Fish struct {
NumScales int
}
func (f Fish) makeNoise() string {
return "glub glub glub"
}
type Zoo struct {
Animals []Animal
}
func main() {
animals := `{"Animals": [{"Name": "Fido"}, {"NumScales": 123}]}`
animalBytes := []byte(animals)
var zoo Zoo
er := json.Unmarshal(animalBytes, &zoo)
if er != nil {
panic(er)
} else {
fmt.Println(zoo)
}
}
但是当我运行它时,我得到了恐慌:json:无法将对象解组为类型为main.Animal"的Go值。我可以改为一个动物园,它的动物是一只名叫Fido的狗,还有一只有123种鱼的鱼?
答案 0 :(得分:5)
根据您提供的当前条件,没有直接的方法可以实现您想要的效果。 @ eduncan911提供了一种非常通用的方法,但是,如果您能够稍微调整JSON
输入,则可以使用以下方法实现它。
核心思想是使用json.RawMessage
作为缓冲区来延迟解组,直到它知道要解组的类型为止。
首先,将JSON
输入调整为以下内容:
{
"Animals": [{
"Type": "dog",
"Property": {
"Name": "Fido"
}
},{
"Type": "fish",
"Property": {
"NumScales": 123
}
}]
}
从我所看到的情况来看,这个调整并没有使JSON变得更糟,但实际上在可读性方面做得更好。
然后,创建一个新的结构,比如AnimalCard
:
type AnimalCard struct {
Type string
Property json.RawMessage
Animal Animal
}
并将Zoo
修改为
type Zoo struct {
Animals []*AnimalCard
}
现在将你的json解组到动物园,你会得到一个*AnimalCard
的数组。现在你可以遍历zoo数组并根据类型解组它:
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为here。
好问题:)上述解决方案给出的问题不会那么可扩展。如果我们有20只动物,而不仅仅是2只,怎么办?怎么样200? 2000?我们需要一种更通用的方法来实现它。
这次的核心理念是使用reflect
。
首先,我们可以维护一个地图,它将类型名称映射到接口实现:
mapper map[string]Animal{}
然后我们放入动物指针:
mapper["dog"] = &Dog{}
mapper["fish"] = &Fish{}
现在,在我们将JSON解组为AnimalCard
并开始迭代之后,我们使用反射初始化一个新的实例指针并将其解组到其中:
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
}
游乐场示例为here。
答案 1 :(得分:2)
使用json.Unmarshaler
界面创建自定义UnmarshalJSON
方法。然后在该方法中,测试类型转换以查看哪种类型有效,分配它并返回它。
本文末尾的总结: