当前位置: 首页 > 知识库问答 >
问题:

有没有办法让json。Unmarshal()是否基于“type”属性选择结构类型?

卢英叡
2023-03-14

我有一些JSON的形式:

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

我有满足车辆接口的car飞机类型;我希望能够写:

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

…并让JSON用汽车和飞机填充我的车辆切片;相反(不出所料),我只是得到“无法将对象散集到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)

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

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

(请注意,这不是具有未知字段的Unmarshal JSON的副本,因为我希望切片中的每个项目都具有不同的动态类型,并且从“type”属性的值中,我确切地知道需要哪些字段 - 我只是不知道如何告诉json。取消封送如何将“类型”属性值映射到 Go 类型。

共有3个答案

党佐
2023-03-14

Go中的JSON解码和编码实际上在识别嵌入结构中的字段方面出人意料地出色。E、 g.当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。解编拆解器接口。要不必先将字段解码为映射,可以扩展使用嵌入式结构的技巧。

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"`
}

对于封送返回,< code>json。如果有重叠字段,还必须实现Marshaler

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

宣瀚
2023-03-14

两遍方法工作得很好,但是也有mapstructure包的选项,它就是为此而创建的。

明正德
2023-03-14

从类似的问题中获取答案:使用未知字段解组 JSON,我们可以构造几种方法来在 [] 车辆数据结构中解组此 JSON 对象。

“手动处理解组”版本可以通过使用通用的< code >[]map[string]interface { } 数据结构,然后从地图切片构建正确的< code>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包来处理单个结构的解组和类型检查,方法是第二次直接解组到正确的类型中。这一切都可以通过在[]车辆类型上定义一个UnmarshalJSON方法来包装成一个json. Unmarshaler实现,首先将JSON对象拆分为原始消息。

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
}
 类似资料:
  • 在java中,不能使用。compareTo()将基元类型(如int)与其他类型进行比较。 如果两个整数小于零,有没有办法比较?我试图将整数与数组中的点进行比较。 谢啦! 解决方法:抱歉,大家都有一秒钟的大脑抽筋。我忘记了a.compareTo(b)根据第一个值是否小于(返回-1)、等于(返回0)或大于(返回1)返回-1、0或1。我的代码是int.compareTo(数组)

  • 问题内容: 由于某些性能原因,我试图找到一种仅选择所选节点中同级节点的方法。例如, 如果选择了inner1节点,是否可以访问其兄弟节点? 问题答案: 好吧…确定…先拜访父母,再拜访孩子。 或者…使用jQuery: 编辑:Cletus一如既往地鼓舞人心。 我进一步挖掘。这就是jQuery本质上如何获得兄弟姐妹的方式:

  • 假设有一个类库X的输入文件,其中包含一些接口。 为了使用这个库,我需要传递一个与。当然,我可以在源文件中创建相同的接口: 但这样一来,我就有了让它和库中的一个保持最新的负担,而且它可能非常大,并导致大量代码重复。 因此,有没有办法“提取”接口的这个特定属性的类型?类似于(这不起作用,导致“找不到名称I2”错误)。 编辑:在TS操场上玩了一会儿后,我注意到以下代码完全实现了我想要的: 但是,它需要声

  • 所以我正在做一个需要xml模式的小项目,我对这个模式很不熟悉。 我希望能够设置模式以在两组属性之间进行选择,根据我的研究,这在XSD 1.0中是不可能的,但显然是XSD 1.1的一个特性。 目前我正在使用VisualStudio来完成我的工作,它似乎被困在XSD1.0模式中,这是有意义的,因为XSD1.1显然是一个最新的开发。 我的问题是,是否有一个插件/扩展可以让我在Visual Studio中

  • 问题内容: 我想根据一个简单的标准更新一组行,并获取已更改的PK列表。我以为我可以做这样的事情,但担心可能出现的并发问题: 如果将其包装在事务中,是否会发生任何并发问题?还是有更好的方法来做到这一点? 问题答案: 考虑查看OUTPUT子句:

  • 我有一个C#类 产品有名称、价格等属性 我想要一个linq查询,它将返回带有产品数组的产品类别的结构,但仅当产品的属性等于一个值时。例如价格是10的地方。 我试过了 但这返回给我一个产品数组,但我想要产品类别中所有符合条件的产品。 作为一个额外的挑战,我也有多个标准,所以在这个例子中,假设价格等于10,30和50。其中一个标准有两个值,这就增加了复杂性。我要做的只是调用不同的选择标准,然后在结尾处