Trouble mapping json to golang struct

Viewed 352

I have a json stream as follows ...

[
  {
    "page": 1,
    "pages": 7,
    "per_page": "2000",
    "total": 13200
  },
  [
    {
      "indicator": {
        "id": "SP.POP.TOTL",
        "value": "Population, total"
      },
      "country": {
        "id": "1A",
        "value": "Arab World"
      },
      "value": null,
      "decimal": "0",
      "date": "2019"
    },
    {
      "indicator": {
        "id": "SP.POP.TOTL",
        "value": "Population, total"
      },
      "country": {
        "id": "1A",
        "value": "Arab World"
      },
      "value": "419790588",
      "decimal": "0",
      "date": "2018"
    },
   ...
   ]
]

And I'm trying to decode it ... so I have the following struct ... but I keep getting "cannot unmarshal array into Go value of type struct { P struct ... "

type Message []struct {
        P struct {
                Page int
        }
        V []struct {
                Indicator struct {
                        Id    string
                        Value string
                }
                Country struct {
                        Value string
                }
                Value   string
                Decimal string
                Date    string
        }
}

My struct looks to match the json ... but obviously not! Any ideas?

3 Answers

Since your JSON array have two different types first unmarshal them into a slice of json.RawMessage which is []byte as underlying type so that we can unmarshal again JSON array data.

So unmarshal data for P and V struct type using index directly (predict) or detect if object(starting with '{') then unmarshal into P and array(starting with '[') then unmarshal into V. Now prepare your Message using those data.

type Message struct {
    PageData P
    ValData  []V
}

type P struct {
    Page int
}

type V struct {
    Indicator struct {
        Id    string
        Value string
    }
    Country struct {
        Value string
    }
    Value   string
    Decimal string
    Date    string
}

func main() {

    var rawdata []json.RawMessage
    json.Unmarshal([]byte(jsonData), &rawdata)
    var pageData P
    json.Unmarshal(rawdata[0], &pageData)
    var valData []V
    json.Unmarshal(rawdata[1], &valData)
    res := Message{pageData, valData}
    fmt.Println(res)
}
var jsonData = `[...]` //your json data

Full code in Go Playground

As poWar said, the JSON you actually have is a list of objects whose types do not conform to each other. You must therefore unmarshal into something capable of holding different object types, such as interface{} or—since there is an outer array—[]interface{}.

You can also, if you like, decode into a []json.RawMessage. The underlying json.RawMessage itself has underlying type []byte so that it's basically the undecoded "inner" JSON. In at least some cases this is going to be more work than just decoding directly to []interface{} and checking each resulting interface, but you can, if you wish, decode to struct once you have the JSON separated out. For instance:

func main() {
    var x []json.RawMessage
    err := json.Unmarshal(input, &x)
    if err != nil {
        fmt.Printf("err = %v\n", err)
        return
    }
    if len(x) != 2 {
        fmt.Println("unexpected input")
        return
    }
    var page struct {
        Page int
    }
    err = json.Unmarshal(x[0], &page)
    if err != nil {
        fmt.Printf("unable to unmarshal page part: %v\n", err)
        return
    }
    fmt.Printf("page = %d\n", page.Page)
    // ...
}

Here on the Go Playground is a more complete example. See also Eklavya's answer.

Looking at your struct, your corresponding JSON should look something like this.

[
    {
        "P": {"page": 1},
        "V": [
            {
                "Indicator": {"Id": ...},
                "Country": {"Value":""},
                "Value": "",
                ...
            }
        ]
    },
    ...
]

The JSON structure you are trying to Unmarshal looks like a list of objects where each object is not of the same type. You can start unmarshalling them into interfaces and defining each interface based on the object being unmarhsalled.

package main

import (
    "encoding/json"
    "log"
)

type Message []interface{}

func main() {
    data := `[{"page":1,"pages":7,"per_page":"2000","total":13200},[{"indicator":{"id":"SP.POP.TOTL","value":"Population, total"},"country":{"id":"1A","value":"Arab World"},"value":null,"decimal":"0","date":"2019"},{"indicator":{"id":"SP.POP.TOTL","value":"Population, total"},"country":{"id":"1A","value":"Arab World"},"value":"419790588","decimal":"0","date":"2018"}]]`

    var m Message
    if err := json.Unmarshal([]byte(data), &m); err != nil {
        log.Fatalf("could not unmarshal")
    }

    log.Printf("message: %v", m)
}

Output:

message: [map[page:1 pages:7 per_page:2000 total:13200] [map[country:map[id:1A value:Arab World] date:2019 decimal:0 indicator:map[id:SP.POP.TOTL value:Population, total] value:<nil>] map[country:map[id:1A value:Arab World] date:2018 decimal:0 indicator:map[id:SP.POP.TOTL value:Population, total] value:419790588]]]

[Edit]: Ideally you should change your JSON to be structured better for unmarshalling. If you do not have control on it, then your corresponding Go structure is just embedded maps of string to interfaces, which you will have to manually type cast and access.

Related