dousuochu7291 2016-04-10 03:57
浏览 42
已采纳

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

I have an interface that declares a method and some structs that implement that interface. Now I want to unmarshal some JSON into instances of these structs. To wit:

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)
    }
}

But when I run that, I get "panic: json: cannot unmarshal object into Go value of type main.Animal". Can I instead get a Zoo whose Animals are a Dog named Fido and a Fish with 123 scales?

  • 写回答

2条回答 默认 最新

  • donglang8008 2016-04-10 08:45
    关注

    There is no straight forward way to achieve what you want based on the current condition you gave us. @eduncan911 provided a very general method, but however, if you are able to tweak the JSON input a bit, you are able to achieve it using the following method.

    The core idea is to use json.RawMessage as a buffer to delay the unmarshal until it knows the type it's gonna unmarshal to.

    Firstly, tweak the JSON input to something like below:

    {
        "Animals": [{
            "Type": "dog",
            "Property": {
                "Name": "Fido"
            }
        },{
            "Type": "fish",
            "Property": {
                "NumScales": 123
            }
        }]
    }
    

    From what I can see, this tweak does not make the JSON worse, but actually make it better in terms of readability.

    Then, create a new struct, say AnimalCard:

    type AnimalCard struct {
        Type string
        Property json.RawMessage
        Animal Animal
    }
    

    And modify your Zoo to

    type Zoo struct {
        Animals []*AnimalCard
    }
    

    Now unmarshal your json to zoo, you will get an array of *AnimalCard. Now you could iterate through zoo array and unmarshal it according to type:

    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 is here.

    What if I got more and more Animals in the Zoo?

    Good question :) The problem the above solution gave won't be that scalable. What if we have 20 animals, not only 2? What if 200? 2000? We need a more general way to do it.

    The core idea this time is to use reflect.

    First, we could maintain a map, which maps a type name to an interface implementation:

    mapper map[string]Animal{}
    

    Then we put in our animals pointers:

    mapper["dog"] = &Dog{}
    mapper["fish"] = &Fish{}
    

    Now, after we unmarshalled the JSON into AnimalCard and start iterating, we use reflection to initialize a new instance pointer and unmarshal into it:

    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
    }
    

    Playground Example is here.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

悬赏问题

  • ¥20 求用stm32f103c6t6在lcd1206上显示Door is open和password:
  • ¥15 apm2.8飞控罗盘bad health,加速度计校准失败
  • ¥15 求解O-S方程的特征值问题给出边界层布拉休斯平行流的中性曲线
  • ¥15 谁有desed数据集呀
  • ¥20 手写数字识别运行c仿真时,程序报错错误代码sim211-100
  • ¥15 关于#hadoop#的问题
  • ¥15 (标签-Python|关键词-socket)
  • ¥15 keil里为什么main.c定义的函数在it.c调用不了
  • ¥50 切换TabTip键盘的输入法
  • ¥15 可否在不同线程中调用封装数据库操作的类