Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unmarshalling a JSON that may or may not return an array?

Tags:

json

go

I'm retrieving JSON from a third party website (home electricity usage), and depending on what I've requested from the site, the JSON returned may or may not be an array. For example, if I request a list of my smart meters, I get this (results truncated, due to large size):

{"gwrcmds":{"gwrcmd":{"gcmd":"SPA_UserGetSmartMeterList","gdata":{"gip":{"version":"1"...

Where gwrcmd is a single element.

But if I request electricity usage for the last half hour, I get this:

{"gwrcmds":{"gwrcmd":[{"gcmd":"DeviceGetChart","gdata":{"gip":{"version":"1" ...

See how gwrcmd is now an array?

Within my Go app, I have a struct that looks like this (again, truncated, as it goes on for a while. There's more sub-structs and properties beneath "Version":

type Response struct {
    Gwrcmds struct {
        Gwrcmd struct {
            Gcmd  string
            Gdata struct {
                Gip struct {
                    Version string

If gwrcmd is an array, Gwrcmd needs to be a []struct { }, but if it's not, it's just a regular old struct { }

The problem is that json.Unmarshal just returns an error if the JSON has an array and the struct does not have a slice (or vice versa).

Would I need to create a second struct that duplicates the first one (except with a []struct { } instead), or is there a better way to do it? I thought of something with interfaces, but I haven't really touched those yet, so I'm not 100% sure on them.

like image 748
Grayda Avatar asked Sep 02 '15 06:09

Grayda


People also ask

What is Unmarshalling in JSON?

To unmarshal a JSON array into a slice, Unmarshal resets the slice length to zero and then appends each element to the slice. As a special case, to unmarshal an empty JSON array into a slice, Unmarshal replaces the slice with a new empty slice.

How to unmarshal JSON in golang?

To parse JSON, we use the Unmarshal() function in package encoding/json to unpack or decode the data from JSON to a struct. Unmarshal parses the JSON-encoded data and stores the result in the value pointed to by v. Note: If v is nil or not a pointer, Unmarshal returns an InvalidUnmarshalError.

How does JSON unmarshal work in golang?

Marshal function, will take JSON data and translate it back into Go data. You provide json. Unmarshal with the JSON data as well as the Go variable to put the unmarshalled data into and it will either return an error value if it's unable to do it, or a nil error value if it succeeded.


2 Answers

Usually, whenever you have a JSON value of unknown type, you will use json.RawMessage to get it, peek into it, and unmarshal it correctly into the corresponding type. A simplified example:

// The A here can be either an object, or a JSON array of objects.
type Response struct {
    RawAWrapper struct {
        RawA json.RawMessage `json:"a"`
    }
    A  A   `json:"-"`
    As []A `json:"-"`
}

type A struct {
    B string
}

func (r *Response) UnmarshalJSON(b []byte) error {
    if err := json.Unmarshal(b, &r.RawAWrapper); err != nil {
        return err
    }
    if r.RawAWrapper.RawA[0] == '[' {
        return json.Unmarshal(r.RawAWrapper.RawA, &r.As)
    }
    return json.Unmarshal(r.RawAWrapper.RawA, &r.A)
}

Playground: http://play.golang.org/p/2d_OrGltDu.

Guessing the content based on the first byte doesn't seem too robust to me though. Usually you'll have some sort of a clue in your JSON (like a length or type field on the same level as the dynamic one) that tells you whether you have an object or an array.

See also:

  • How can you decode multiple message types with golang websockets?
  • Partly JSON unmarshal into a map in Go
like image 119
Ainar-G Avatar answered Sep 20 '22 02:09

Ainar-G


You can try to make custom json unmarshal method, like

func (a *GwrcmCustom) UnmarshalJSON(b []byte) (err error) {
    g, ga := Gwrcmd{}, []Gwrcmd{}
    if err = json.Unmarshal(b, &g); err == nil {
        *a = make([]Gwrcmd, 1)
        []Gwrcmd(*a)[0] = Gwrcmd(g)
        return
    }
    if err = json.Unmarshal(b, &ga); err == nil {
        *a = GwrcmCustom(ga)
        return
    }
    return
}

GwrcmCustom is a custom type, slice of Gwrcm

type GwrcmCustom []Gwrcmd

So we will get slice always

Try this on Go playground

I hope this will help

like image 42
RoninDev Avatar answered Sep 21 '22 02:09

RoninDev