Reputation: 5145
I am requesting an API which returns JSON containing an array of objects. The problem is that the objects can take two forms. They can either be a string or an object. An example result could look like this:
[
{"name": "obj1", "key2": ["a", "b"]},
"obj2",
{"name": "obj3"}
]
A string element "objX"
is equivalent to {"name": "objX"}
.
I want to parse this into a slice of the following type:
type Obj struct {
Name string
Key2 []string
}
How do I do this in a reasonable fashion?
Upvotes: 2
Views: 1971
Reputation:
package main
import (
"encoding/json"
"fmt"
)
type Obj struct {
Name string `json:"name"`
Key2 []string `json:"key2"`
}
func (o *Obj) UnmarshalJSON(b []byte) error {
var name string
if err := json.Unmarshal(b, &name); err == nil {
*o = Obj{}
o.Name = name
return nil
}
type Obj2 Obj
var o2 Obj2
if err := json.Unmarshal(b, &o2); err != nil {
return err
}
*o = Obj(o2)
return nil
}
const payload = `[
{"name": "obj1", "key2": ["a", "b"]},
"obj2",
{"name": "obj3"}
]
`
func main() {
var objs []*Obj
if err := json.Unmarshal([]byte(payload), &objs); err != nil {
panic(err)
}
for _, obj := range objs {
fmt.Printf("Name:%v, Key2:%v\n", obj.Name, obj.Key2)
}
}
https://play.golang.org/p/rmtSOpkYqp
Upvotes: 2
Reputation: 418585
Another solution is to unmarshal the varying elements of your JSON arrray into values of types you expect, and process / create an Obj
wrapper for the string
values should you need it.
Since your input is a JSON array, it can be unmarshalled into an array or slice. And since the elements are of different types, the element type of the Go array or slice must be interface{}
.
Unmarshaling into a value of type []interface{}
would result in the encoding/json
package choosing map[string]interface{}
for the object elements. But if you populate the target slice prior with elements of the types you wish to unmarshal into, the json
package will obediently use those:
func main() {
res := []interface{}{
&Obj{},
"",
&Obj{},
}
if err := json.Unmarshal([]byte(src), &res); err != nil {
panic(err)
}
for _, v := range res {
fmt.Printf("type = %-10T value = %+q\n", v, v)
}
}
const src = `[
{"name": "obj1", "key2": ["a", "b"]},
"obj2",
{"name": "obj3"}
]`
Output (try it on the Go Playground):
type = *main.Obj value = &{"obj1" ["a" "b"]}
type = string value = "obj2"
type = *main.Obj value = &{"obj3" []}
Upvotes: 0