Mathias Bak
Mathias Bak

Reputation: 5145

Parsing multiple JSON types into the same struct

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

Answers (2)

user142162
user142162

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

icza
icza

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

Related Questions