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