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.
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:
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