我有一个接口,它声明了一个方法和一些实现该接口的结构。现在,我想将一些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)
}
}
但当我运行它时,我会得到"panic:json:无法将对象分解为main.Animal类型的Go值"。我可以得到一个Zoo吗?它的动物是一只名叫Fido的狗和一条有123个鳞片的鱼?
根据您给我们的当前条件,没有直接的方法可以实现您想要的。@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分解为zoo,您将得到一个*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
}
}
游乐场Exmaple在这里。
如果动物园里有越来越多的动物呢
好问题:)上述解决方案给出的问题不会那么可扩展。如果我们有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
}
游乐场示例在这里。
使用json.Unmarshaler
接口创建自定义UnmarshalJSON
方法。然后在该方法中,测试类型转换,看看哪个类型有效,分配它,然后返回它
很好的总结在这篇文章的结尾:
http://attilaolah.eu/2013/11/29/json-decoding-in-go/