Go - 如何处理 JSON 具有不同类型属性的响应

Go - How to deal with JSON response that has attribute that can be of different types

假设我有以下结构

type Response struct {
    ID string  `json:"id"`
    Edited int `json:"edited"`
}

type Responses []Response

然后假设我向 API 发送请求,但我的问题是 API 文档,测试告诉我 edited 值可以作为boolint。这显然让 Go 感到不安,并且在将响应主体解码为结构时抛出错误。

// ... http GET
// response [{"edited": true, id: 1}, {"edited": 1683248234.0, id: 2}]

r := Responses{}
err := json.NewDecoder(resp.Body).Decode(&r)
if err != nil {
    return nil, err
}
defer resp.Body.Close()

如上无法自动加载到struct中的情况如何处理?我假设我需要先将它做成一个接口,然后过滤切片并在它们自己的结构中处理两种不同的响应类型?但是我不能把它们结合起来!

所以我正在考虑使字段符合其中一个,bool 或 int。

n.b。这与 Reddit API 有关,其中某些字段(例如 editedcreatedcreated_utc 没有符合类型。

@mkopriva 所述,处理不同类型变量的最简单方法是使用 interface{} 类型:

const resp = `[{"edited": true, "id": 1}, {"edited": 1683248234.0, "id": 2}, {"id": 3}]`

type Response struct {
    ID     int         `json:"id"`
    Edited interface{} `json:"edited"`
}

type Responses []Response
r := Responses{}
err := json.Unmarshal([]byte(resp), &r)
if err != nil {
    log.Fatal(err)
}
for _, response := range r {
    switch response.Edited.(type) {
    case float64:
        fmt.Println("float64")
    case bool:
        fmt.Println("bool")
    default:
        fmt.Println("invalid")
    }
}

PLAYGROUND

您还可以使用自定义 json.Unmarshaler 实现定义新的 type

type Response struct {
    ID     int    `json:"id"`
    Edited Edited `json:"edited"`
}

type Edited struct {
    BoolVal  *bool
    FloatVal *float64
}

func (e *Edited) UnmarshalJSON(data []byte) error {
    boolVal, err := strconv.ParseBool(string(data))
    if err == nil {
        e.BoolVal = &boolVal
        return nil
    }
    floatVal, err := strconv.ParseFloat(string(data), 64)
    if err == nil {
        e.FloatVal = &floatVal
        return nil
    }
    return errors.New("undefined type")
}
r := Responses{}
err := json.Unmarshal([]byte(resp), &r)
if err != nil {
    log.Fatal(err)
}
for _, response := range r {
    edited := response.Edited
    switch {
    case edited.FloatVal != nil:
        fmt.Println("float64")
    case edited.BoolVal != nil:
        fmt.Println("bool")
    default:
        fmt.Println("invalid")
    }
}

PLAYGROUND