Best way to represent this JSON in Go?

528 Views Asked by At

I am writing an endpoint to return data for Geckoboard, it excepts a format like so:

{
  "item": [
    {
      "value": "274057"
    },
    [
      "38594",
      "39957",
      "35316",
      "35913",
      "36668",
      "45660",
      "41949"
    ]
  ]
}

"item" is an array of varying structures. How would I represent this data in Go?

Note: this is not on how I would unmarshal this, I need to generate this format.

2

There are 2 best solutions below

3
On BEST ANSWER

This stuff is easier than you might think. It's just not that well documented for the casual reader. I would recommend ffjson over normal json tho. It's made up in such a manner that you don't need to change your syntax other than the library name.

It's easy as this:

type User struct {
    Id      int    `json:'id'`
    Name    string `json:name`
    SomeId1 int    `json:some_id_1`
    SomeId2 int    `json:some_id_2`
    SomeId3 int    `json:some_id_3`
    SomeId4 int    `json:some_id_4`
}

item := map[string]User{}
for i := 0; i < 10; i++ {
    item[strconv.itoa(i)] = User{i, "Username X", 38393, 29384, 12393, 123981}
}
buf, err := ffjson.Marshal(&item)

The downside of structs (even in ffjson still) is that reflection will always be used, which, in times that you're in need of high performance, you will waste a lot of CPU cycles. ffjson is 2-3 times faster than normal json when you keep it to maps. This way the library can compile every data-structure you marshal and re-use it instead of constantly inspecting the data-integrity/structure with reflect.

0
On

There is an easy way to create a value of some data structure where you know the JSON output you want to generate/duplicate:

Take the output you want to generate, and unmarshal it into a map[string]interface{}. You will get a map value which when you marshal will result in your desired output. When you unmarshal an expected output, you can inspect the result map value to know what you need to create in order for your expected output.

This works in your case too. Here is the code:

var m map[string]interface{}
err := json.Unmarshal([]byte(input), &m)
if err != nil {
    panic(err)
}
fmt.Printf("%+v\n", m)

res, err := json.MarshalIndent(m, "", "  ")
if err != nil {
    panic(err)
}
fmt.Println(string(res))

Where input is your JSON input:

const input = `{
  "item": [
    {
      "value": "274057"
    },
    [
      "38594",
      "39957",
      "35316",
      "35913",
      "36668",
      "45660",
      "41949"
    ]
  ]
}`

This program generates the same output as your required output (or input):

map[item:[map[value:274057] [38594 39957 35316 35913 36668 45660 41949]]]
{
  "item": [
    {
      "value": "274057"
    },
    [
      "38594",
      "39957",
      "35316",
      "35913",
      "36668",
      "45660",
      "41949"
    ]
  ]
}

Try the complete application on the Go Playground.

Analyzing your unmarshaled map value:

Obviously it has a key "item", its value's type:

fmt.Printf("%T\n", m["item"]); // Prints []interface{}

So it's a slice. It has 2 values, their types:

fmt.Printf("%T\n", m["item"].([]interface{})[0]); // map[string]interface{}
fmt.Printf("%T\n", m["item"].([]interface{})[1]); // []interface{}

So the slice contains 2 values:

  • a map (the "value" : "274057" pair)
  • and another slice (the list of ids or numbers)

Here is the Go code to reproduce the same map value:

m := map[string]interface{}{
    "item": []interface{}{
        map[string]interface{}{"value": "274057"},
        []interface{}{"38594", "39957", "35316", "35913", "36668", "45660", "41949"},
    },
}

Marshaling this produces the same JSON output.