【问题标题】:Parsing multiple JSON types into the same struct将多个 JSON 类型解析为同一个结构
【发布时间】:2017-11-01 14:31:11
【问题描述】:

我正在请求一个 API,它返回包含对象数组的 JSON。问题是对象可以采取两种形式。它们可以是字符串或对象。示例结果可能如下所示:

[
    {"name": "obj1", "key2": ["a", "b"]},
    "obj2",
    {"name": "obj3"}
]

一个字符串元素"objX"等价于{"name": "objX"}

我想将其解析为以下类型的切片:

type Obj struct {
    Name string
    Key2 []string
}

我该如何以合理的方式做到这一点?

【问题讨论】:

    标签: json go


    【解决方案1】:
    package main
    
    import (
        "encoding/json"
        "fmt"
    )
    
    type Obj struct {
        Name string   `json:"name"`
        Key2 []string `json:"key2"`
    }
    
    func (o *Obj) UnmarshalJSON(b []byte) error {
        var name string
        if err := json.Unmarshal(b, &name); err == nil {
            *o = Obj{}
            o.Name = name
            return nil
        }
    
        type Obj2 Obj
        var o2 Obj2
        if err := json.Unmarshal(b, &o2); err != nil {
            return err
        }
        *o = Obj(o2)
        return nil
    }
    
    const payload = `[
        {"name": "obj1", "key2": ["a", "b"]},
        "obj2",
        {"name": "obj3"}
    ]
    `
    
    func main() {
        var objs []*Obj
        if err := json.Unmarshal([]byte(payload), &objs); err != nil {
            panic(err)
        }
        for _, obj := range objs {
            fmt.Printf("Name:%v, Key2:%v\n", obj.Name, obj.Key2)
        }
    }
    

    https://play.golang.org/p/rmtSOpkYqp

    【讨论】:

    • Obj 结构有很多属性。有没有办法避免重复这么多次?
    • @MathiasBak 是的,使用像type Obj2 Obj 这样的类型声明,并使用类型转换将结果分配给*o*o = Obj(obj)。请参阅Go Playground 上的示例。我个人会推荐这个而不是复制结构。或者更好:声明一个新类型type Obj2 Obj,并将o 本身分配给*Obj2 类型的值:var obj *Obj2 = (*Obj2)(o),这样你甚至不需要复制结果,json 包将解组到 o 本身。请参阅Playground。这会在性能方面获胜。
    【解决方案2】:

    另一种解决方案是将 JSON 数组的不同元素解组为您期望的类型的值,并在需要时为 string 值处理/创建一个 Obj 包装器。

    由于您的输入是 JSON 数组,因此可以将其解组为数组或切片。而且由于元素的类型不同,所以Go数组或切片的元素类型必须是interface{}

    解组为[]interface{} 类型的值将导致encoding/json 包为对象元素选择map[string]interface{}。但是,如果您先使用您希望解组的类型的元素填充目标切片,json 包将乖乖地使用这些:

    func main() {
        res := []interface{}{
            &Obj{},
            "",
            &Obj{},
        }
        if err := json.Unmarshal([]byte(src), &res); err != nil {
            panic(err)
        }
    
        for _, v := range res {
            fmt.Printf("type = %-10T value = %+q\n", v, v)
        }
    }
    
    const src = `[
        {"name": "obj1", "key2": ["a", "b"]},
        "obj2",
        {"name": "obj3"}
    ]`
    

    输出(在Go Playground上试试):

    type = *main.Obj  value = &{"obj1" ["a" "b"]}
    type = string     value = "obj2"
    type = *main.Obj  value = &{"obj3" []}
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-08-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多