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

cpc*_*len 4 json interface go

我有一些形式的JSON:

[{
    "type": "car",
    "color": "red",
    "hp": 85,
    "doors": 4
}, {
    "type": "plane",
    "color": "blue",
    "engines": 3
}]
Run Code Online (Sandbox Code Playgroud)

我有类型carplane满足车辆接口; 我想能够写:

var v []vehicle
e := json.Unmarshal(myJSON, &v)
Run Code Online (Sandbox Code Playgroud)

...让JSON用汽车和飞机填满我的车辆; 相反(并且不出所料)我只是得到"不能将对象解组为类型为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)
Run Code Online (Sandbox Code Playgroud)

(请注意,我其实是在完全不感兴趣t的领域carplane-它可以被省略,因为这些信息会,如果有人成功地回答了这个问题,在动态类型的对象的隐v).

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

(请注意,这不是Unmarshal JSON与未知字段的重复,因为我希望切片中的每个项目具有不同的动态类型,并且从'type'属性的值我确切知道要期望的字段 - 我只是不要我不知道如何告诉json.Unmarshal如何将'type'属性值映射到Go类型.)

Jim*_*imB 7

从类似的问题中解答:用未知字段解组JSON,我们可以构造一些方法来解析[]vehicle数据结构中的这个JSON对象.

可以使用通用[]map[string]interface{}数据结构,然后vehicles从地图切片构建正确的"手动处理Unmarshal"版本.为简洁起见,此示例确实省略了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)
}
Run Code Online (Sandbox Code Playgroud)

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

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
}
Run Code Online (Sandbox Code Playgroud)