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
值可以作为bool
或 int
。这显然让 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 有关,其中某些字段(例如 edited
、created
、created_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")
}
}
您还可以使用自定义 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")
}
}
假设我有以下结构
type Response struct {
ID string `json:"id"`
Edited int `json:"edited"`
}
type Responses []Response
然后假设我向 API 发送请求,但我的问题是 API 文档,测试告诉我 edited
值可以作为bool
或 int
。这显然让 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 有关,其中某些字段(例如 edited
、created
、created_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")
}
}
您还可以使用自定义 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")
}
}