2017-03-10 48 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 
} 
相關問題