gopher
gopher

Reputation: 21

golang unmarshal map[string]interface{} to a struct containing an array with meta

I have the following json data coming through an API. I want to unmarshal this data into a different way of structure as it is defined below. How can I do it in an elegant way?

{
    "_meta": {
        "count": 2,
        "total": 2
    },
    "0": {
        "key": "key0",
        "name": "name0"
    },
    "1": {
        "key": "key1",
        "name": "name1"
    },
    "2": {
        "key": "key2",
        "name": "name2"
    }
    // It goes on..
}
type Data struct {
   Meta Meta `json:"_meta,omitempty"`
   Elements []Element
}

type Element struct {
   Key string
   Name string
}

type Meta struct{
   Count int
   Total int
}

Upvotes: 1

Views: 1912

Answers (1)

ramrunner
ramrunner

Reputation: 1372

This can be quite tricky because you have a json object that holds everything. So i went with the approach of unmarshalling to map of string to *json.RawMessage and then fixing the struct from there.

To do that you will be using a custom Unmarshaler and the benefit of it is that you delay the actual parsing of the inner messages until you need them.

So for example if your meta field was wrong or the numbers it said didn't match the length of the map-1 you could exit prematurely.

package main

import (
        "encoding/json"
        "fmt"
)

type jdata map[string]*json.RawMessage

type data struct {
        Meta     Meta
        Elements []Element
}

//Element is a key val assoc
type Element struct {
        Key  string
        Name string
}

//Meta holds counts and total of elems
type Meta struct {
        Count int
        Total int
}

var datain = []byte(`
{
    "_meta": {
        "count": 2,
        "total": 2
    },
    "0": {
        "key": "key0",
        "name": "name0"
    },
    "1": {
        "key": "key1",
        "name": "name1"
    },
    "2": {
        "key": "key2",
        "name": "name2"
    }
}`)

func (d *data) UnmarshalJSON(buf []byte) (err error) {
        var (
                meta *json.RawMessage
                ok   bool
        )
        jdata := new(jdata)

        if err = json.Unmarshal(buf, jdata); err != nil {
                return
        }
        if meta, ok = (*jdata)["_meta"]; !ok {
                return fmt.Errorf("_meta field not found in JSON")
        }
        if err = json.Unmarshal(*meta, &d.Meta); err != nil {
                return
        }
        for k, v := range *jdata {
                if k == "_meta" {
                        continue
                }
                elem := &Element{}
                if err = json.Unmarshal(*v, elem); err != nil {
                        return err
                }
                d.Elements = append(d.Elements, *elem)
        }
        return nil

}

func main() {
        data := &data{}
        if err := data.UnmarshalJSON(datain); err != nil {
                panic(err)
        }
        fmt.Printf("decoded:%v\n", data)
}

Upvotes: 3

Related Questions