json - 有没有办法让 json.Unmarshal() 根据 "type"属性选择结构类型?

标签 json go interface

我有一些 JSON 格式:

[{
    "type": "car",
    "color": "red",
    "hp": 85,
    "doors": 4
}, {
    "type": "plane",
    "color": "blue",
    "engines": 3
}]

我有类型 carplane 满足车辆接口(interface);我希望能够写:

var v []vehicle
e := json.Unmarshal(myJSON, &v)

...并让 JSON 用汽车和飞机填充我的车辆 slice ;相反(毫不奇怪)我只是得到“无法将对象解码为 main.vehicle 类型的 Go 值”。

作为引用,以下是所涉及类型的合适定义:

type vehicle interface {
    vehicle()
}

type car struct {
    Type  string
    Color string
    HP    int
    Doors int
}

func (car) vehicle() { return }

type plane struct {
    Type    string
    Color   string
    Engines int
}

func (plane) vehicle() { return }

var _ vehicle = (*car)(nil)
var _ vehicle = (*plane)(nil)

(请注意,我实际上对 carplane 上的 t 字段完全不感兴趣 - 它可以省略,因为此信息将,如果有人成功回答了这个问题,则隐含在 v 中对象的动态类型中。)

有没有办法让 JSON umarhsaller 根据正在解码的数据的某些部分内容(在本例中为类型字段)选择要使用的类型?

(请注意,这不是 Unmarshal JSON with unknown fields 的副本,因为我希望 slice 中的每个项目都具有不同的动态类型,并且根据我知道的“类型”属性的值 < em>确切地期望的字段——我只是不知道如何告诉 json.Unmarshal 如何将“类型”属性值映射到 Go 类型。)

最佳答案

从类似问题中获取答案:Unmarshal JSON with unknown fields ,我们可以构建一些方法来在 []vehicle 数据结构中取消对这个 JSON 对象的编码。

“使用手动处理解码”版本可以通过使用通用 []map[string]interface{} 数据结构来完成,然后构建正确的 vehicles map 的 slice 。为简洁起见,此示例确实省略了 json 包本应完成的针对缺失或错误键入字段的错误检查。

https://play.golang.org/p/fAY9JwVp-4

func NewVehicle(m map[string]interface{}) vehicle {
    switch m["type"].(string) {
    case "car":
        return NewCar(m)
    case "plane":
        return NewPlane(m)
    }
    return nil
}

func NewCar(m map[string]interface{}) *car {
    return &car{
        Type:  m["type"].(string),
        Color: m["color"].(string),
        HP:    int(m["hp"].(float64)),
        Doors: int(m["doors"].(float64)),
    }
}

func NewPlane(m map[string]interface{}) *plane {
    return &plane{
        Type:    m["type"].(string),
        Color:   m["color"].(string),
        Engines: int(m["engines"].(float64)),
    }
}

func main() {
    var vehicles []vehicle

    objs := []map[string]interface{}{}
    err := json.Unmarshal(js, &objs)
    if err != nil {
        log.Fatal(err)
    }

    for _, obj := range objs {
        vehicles = append(vehicles, NewVehicle(obj))
    }

    fmt.Printf("%#v\n", vehicles)
}

我们可以再次利用 json 包,通过直接第二次解码为正确的类型来处理单个结构的解码和类型检查。这可以通过在 []vehicle 类型上定义一个 UnmarshalJSON 方法来首先拆分 JSON 来包装到一个 json.Unmarshaler 实现中对象转换为原始消息。

https://play.golang.org/p/zQyL0JeB3b

type Vehicles []vehicle


func (v *Vehicles) UnmarshalJSON(data []byte) error {
    // this just splits up the JSON array into the raw JSON for each object
    var raw []json.RawMessage
    err := json.Unmarshal(data, &raw)
    if err != nil {
        return err
    }

    for _, r := range raw {
        // unamrshal into a map to check the "type" field
        var obj map[string]interface{}
        err := json.Unmarshal(r, &obj)
        if err != nil {
            return err
        }

        vehicleType := ""
        if t, ok := obj["type"].(string); ok {
            vehicleType = t
        }

        // unmarshal again into the correct type
        var actual vehicle
        switch vehicleType {
        case "car":
            actual = &car{}
        case "plane":
            actual = &plane{}
        }

        err = json.Unmarshal(r, actual)
        if err != nil {
            return err
        }
        *v = append(*v, actual)

    }
    return nil
}

关于json - 有没有办法让 json.Unmarshal() 根据 "type"属性选择结构类型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42721732/

相关文章:

ruby-on-rails - rails 4 : Saving value as JSON string object/array in PostgreSQL

json - 分片大小的 Elasticsearch GET API

go - 单元测试从结构属性模拟接口(interface)

pointers - 存储为值的接口(interface);无法更新结构字段的方法

java - 如何告诉 Java 接口(interface)实现了 Comparable?

java - 尽管类在 switch block 中实例化,但它只能调用接口(interface)方法

php循环遍历json,不同的属性名

json - PostgreSQL 无法在标量上调用 json_object_keys

unit-testing - 如何在 go-sqlmock 中添加多个结果集?

go - 安装 Go from source to custom prefix