2017-03-10 46 views
2

我有形式的一些JSON:有没有办法让json.Unmarshal()选择基于“type”属性的结构类型?

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

我有类型carplane和满足车辆接口;我希望能够写出:

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

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

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

有没有办法让JSON umarhsaller根据部分内容选择使用哪种类型(在这种情况下,数据类型字段)被解码?

(请注意,这是Unmarshal JSON with unknown fields重复的,因为我想在片每个项目有不同的动态类型,并从“类型”属性的值,我知道究竟期待哪些领域 - 我只是不知道如何告诉json.Unmarshal如何将'type'属性值映射到Go类型上。)

+0

是的,你可以实现'json.Unamrshaler',但它取决于你希望你的最终数据结构的样子。你期望“车辆”是什么? – JimB

+0

如问题所示,我希望我的最终数据结构'v'是'[] vehicle'(或'[] * vehicle'',这也可以)'v [0]'是一个'car'和'v [1]'是一架飞机。 – cpcallen

+0

@JimB:你会如此重视这个问题吗?我相信我已经充分澄清,以证明它不是“完全重复的”[Unmarshal JSON with unknown fields](https:// stackoverflow。COM /问题/ 33436730 /解组JSON-与未知场)。 – cpcallen

回答

2

从类似问题Unmarshal JSON with unknown fields的答案中,我们可以构造几种方法来解开这个JSON对象在一个[]vehicle数据结构中。

“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包直接解编第二次为正确的类型照顾个体结构的拆封和类型检查。这可以通过在[]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 
} 
相关问题