我可以将JSON分解为接口的实现程序吗



我有一个接口,它声明了一个方法和一些实现该接口的结构。现在,我想将一些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/

最新更新