为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

Go
德玛西亚99 2022-01-04 10:29:13
我有一个声明方法的接口和一些实现该接口的结构。现在我想将一些 JSON 解组到这些结构的实例中。以机智:package mainimport (    "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: cannot unmarshal object into Go value of type main.Animal”。我可以换一个动物园,它的动物是一只名叫 Fido 的狗和一条 123 鳞片的鱼吗?
查看完整描述

2 回答

?
繁星coding

TA贡献1797条经验 获得超4个赞

根据您提供给我们的当前条件,没有直接的方法可以实现您想要的目标。@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. 现在您可以遍历动物园数组并根据类型对其进行解组:


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

    }

}

游乐场示例在这里。


如果动物园里的动物越来越多怎么办?

好问题 :) 上述解决方案给出的问题不会那么可扩展。如果我们有 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

}

游乐场示例在这里


查看完整回答
反对 回复 2022-01-04
?
Qyouu

TA贡献1786条经验 获得超11个赞

使用json.Unmarshaler接口创建自定义UnmarshalJSON方法。然后在该方法中,测试类型转换以查看哪种类型有效,分配并返回它。

这篇文章末尾的好总结:

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


查看完整回答
反对 回复 2022-01-04
  • 2 回答
  • 0 关注
  • 193 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号