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.
CodePudding user response:
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")
}
}
Also you can define new type with custom json.Unmarshaler implementation:
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("edited has 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")
}
}
