如何处理具有可能是不同类型的属性的 JSON 响应?

huangapple go评论87阅读模式
英文:

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

问题

假设我有以下结构体:

type Response struct {
    ID     string `json:"id"`
    Edited interface{} `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()

在无法自动加载到结构体中的情况下,我该如何处理上述情况?我猜想我需要先将其加载到一个接口中,然后过滤切片并处理各自不同的Response类型的结构体?但是然后我无法将它们合并!

因此,我考虑将字段转换为boolint类型之一。

注:这与Reddit API相关,其中一些字段(如editedcreatedcreated_utc)没有符合规范的类型。

英文:

Let's say I have the following struct

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

type Responses []Response

Then lets say I send a request to an API, but my issue is the API docs and from testing have told me that the edited value can come back as a bool or as a int. This obviously upsets Go and it throws an error when decoding the response body into the struct.

// ... 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()

How can I handle the above situation where I can't automatically load it into a struct? I'm assuming I'd need to do it into an interface first then filter the slice and handle the two different Response types in their own structs? But then I can't combine them!

So I'm thinking of conforming the field to one or the other, bool or int.

n.b. this relates to the Reddit API where some of the fields such as edited, created, created_utc don't have conforming types.

答案1

得分: 3

根据@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 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

英文:

As told @mkopriva, the simplest way to handle different type of variable is use interface{} type:

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

<kbd>PLAYGROUND</kbd>

Also you can define new type with custom json.Unmarshaler implementation:

type Response struct {
	ID     int    `json:&quot;id&quot;`
	Edited Edited `json:&quot;edited&quot;`
}

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 = &amp;boolVal
		return nil
	}
	floatVal, err := strconv.ParseFloat(string(data), 64)
	if err == nil {
		e.FloatVal = &amp;floatVal
		return nil
	}
	return errors.New(&quot;undefined type&quot;)
}
r := Responses{}
err := json.Unmarshal([]byte(resp), &amp;r)
if err != nil {
	log.Fatal(err)
}
for _, response := range r {
	edited := response.Edited
	switch {
	case edited.FloatVal != nil:
		fmt.Println(&quot;float64&quot;)
	case edited.BoolVal != nil:
		fmt.Println(&quot;bool&quot;)
	default:
		fmt.Println(&quot;invalid&quot;)
	}
}

<kbd>PLAYGROUND</kbd>

huangapple
  • 本文由 发表于 2022年2月4日 12:06:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/70981298.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定