我可以将JSON解组为接口的实现者吗?

时间:2016-04-10 03:57:55

标签: json go

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

2 个答案:

答案 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方法。然后在该方法中,测试类型转换以查看哪种类型有效,分配它并返回它。

本文末尾的总结:

http://attilaolah.eu/2013/11/29/json-decoding-in-go/