1

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.

icza
  • 389,944
  • 63
  • 907
  • 827
Elliot Lings
  • 1,096
  • 1
  • 9
  • 16
  • 1
    possible duplicate of [How to unmarshall an array of different types correctly?](http://stackoverflow.com/questions/13364181/how-to-unmarshall-an-array-of-different-types-correctly) – Ainar-G May 13 '15 at 12:00
  • I need to do the opposite, marshall it (and how I would structure it), is the possible duplicate still relevant? – Elliot Lings May 13 '15 at 12:04
  • You probably mean the data-structure. I use it like this `map[string]interface{}` for each depth and `map[string]string` for each entry. Hope that helps you out. And please use `ffjson`. The native `json` is still very slow. –  May 13 '15 at 12:08
  • Yes, the basic principle is still the same. The simplest way you can represent your data in Go is `struct{ Item []interface{} }`. I may add an answer with an example, if you need it. – Ainar-G May 13 '15 at 12:09
  • Avoid structs if they're not necessary tho. When you really want performance and let the `reflect` of the marshaller kick in you will have extreme slowdowns. Just saying :) –  May 13 '15 at 12:10
  • Thanks peeps, that's helpful and interesting. @Allendar are you saying structs are slow for JSON marshalling? I'm trying to represent the graphs as Go types to make it easier to understand, I have something like this http://pastebin.com/Vxn0WAgU – Elliot Lings May 13 '15 at 12:14
  • That's no problem tho. The slowdown is spoken in Go terms, so if your board-system is ok with tens of requests per second and not hundreds or thousands then you don't really need to worry. –  May 13 '15 at 12:17
  • Yeh that's cool. Performance isn't critical for this; never knew structs can slow down JSON marshalling though. Thanks for the insight – Elliot Lings May 13 '15 at 12:18

2 Answers2

1

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.

royhowie
  • 11,075
  • 14
  • 50
  • 67
  • That's really helpful, thank you. I suppose a tradeoff to using maps is the code readability suffers a bit. Our application isn't performance critical so use of structs isn't too much of a problem. But thanks for the insight, I'm sure this will come in handy in one day! :) – Elliot Lings May 13 '15 at 12:27
  • `string(i)` isn't doing what I think you intended. – Dave C May 13 '15 at 14:35
  • Haha thanks, fixed it :) Those would've been some evil bytes ^^ –  May 13 '15 at 16:14
1

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.

icza
  • 389,944
  • 63
  • 907
  • 827