我有一些 JSON 格式:
[{
"type": "car",
"color": "red",
"hp": 85,
"doors": 4
}, {
"type": "plane",
"color": "blue",
"engines": 3
}]
我有类型 car
和 plane
满足车辆接口(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)
(请注意,我实际上对 car
和 plane
上的 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/