我有一些形式的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类型。)
答案 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.A
和T.B
属性。
如果A
和B
之间存在重叠的字段,或者只是为了更好地丢弃无效的字段和类型组合,则需要实现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
。
答案 2 :(得分:0)
两次通过方法很好用,但是mapstructure程序包中也有一个选项可以用来实现此目的。
答案 3 :(得分:-1)
如果属性是一个字符串,你可以使用 .(string) 来转换属性,因为源是一个接口。
您可以通过以下方式使用它:
v["type"].(string)