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

时间:2017-03-10 15:15:19

标签: json go interface

我有一些形式的JSON:

div{
    pointer-events: auto;
}
div:not(.other-class1):not(.other-class2):not(p){
    pointer-events: none;
}

我有满足车辆界面的[{ "type": "car", "color": "red", "hp": 85, "doors": 4 }, { "type": "plane", "color": "blue", "engines": 3 }] car类型;我希望能写下来:

plane

...让JSON用汽车和飞机填满我的车辆;相反(并且不出所料)我只是得到“不能将对象解组为类型为main.vehicle的Go值”。

供参考,以下是所涉及类型的合适定义:

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

(请注意,我实际上对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) t上的car字段完全不感兴趣 - 可以省略它,因为如果有人成功回答了这个问题,这些信息将是隐含在plane中的对象的动态类型中。)

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

(请注意,这是Unmarshal JSON with unknown fields的副本,因为我希望切片中的每个项目具有不同的动态类型,并且根据'type'属性的值我知道完全期待什么字段 - 我只是不知道如何告诉json.Unmarshal如何将'type'属性值映射到Go类型。)

4 个答案:

答案 0 :(得分:6)

从类似问题Unmarshal JSON with unknown fields中得到答案,我们可以构建一些方法来解析[]vehicle数据结构中的JSON对象。

" Unmarshal with Manual Handling"可以使用通用[]map[string]interface{}数据结构完成版本,然后从地图切片构建正确的vehicles。为简洁起见,此示例确实省略了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包来处理单个结构的解组和类型检查,方法是将第二次直接解组为正确的类型。通过在json.Unmarshaler类型上定义UnmarshalJSON方法,首先将JSON对象拆分为原始消息,这可以全部包含在[]vehicle实现中。

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
}

答案 1 :(得分:1)

Go中的JSON解码和编码实际上在识别嵌入式结构内部的字段方面出奇的出色。例如。当类型A和类型B之间没有重叠字段时,对以下结构进行解码或编码即可:

type T struct{
    Type string `json:"type"`
    *A
    *B
}

type A struct{
    Baz int `json:"baz"`
}

type B struct{
    Bar int `json:"bar"`
}

请注意,如果在以上示例的JSON中同时设置了“ baz”和“ bar”,则会同时设置T.AT.B属性。

如果AB之间存在重叠的字段,或者只是为了更好地丢弃无效的字段和类型组合,则需要实现json.Unmarshaler接口。不必先将字段解码为映射,就可以扩展使用嵌入式结构的技巧。

type TypeSwitch struct {
    Type string `json:"type"`
}

type T struct {
    TypeSwitch
    *A
    *B
}

func (t *T) UnmarshalJSON(data []byte) error {
    if err := json.Unmarshal(data, &t.TypeSwitch); err != nil {
        return err
    }
    switch t.Type {
    case "a":
        t.A = &A{}
        return json.Unmarshal(data, t.A)
    case "b":
        t.B = &B{}
        return json.Unmarshal(data, t.B)
    default:
        return fmt.Errorf("unrecognized type value %q", t.Type)
    }

}

type A struct {
    Foo string `json:"bar"`
    Baz int    `json:"baz"`
}

type B struct {
    Foo string `json:"foo"`
    Bar int    `json:"bar"`
}

对于封送,如果字段重叠,还必须实现json.Marshaler

完整示例:https://play.golang.org/p/UHAdxlVdFQQ

答案 2 :(得分:0)

两次通过方法很好用,但是mapstructure程序包中也有一个选项可以用来实现此目的。

答案 3 :(得分:-1)

如果属性是一个字符串,你可以使用 .(string) 来转换属性,因为源是一个接口。
您可以通过以下方式使用它:
v["type"].(string)