It's really unclear what you're asking here. Are you asking why the JSON output doesn't match what you expect? Are you unsure how to initialise/set values on the Index
field of type []struct{...}
?
Because it's quite unclear, I'll attempt to explain why your JSON output may appear to have missing fields (or why not all fields are getting populated), how you can initialise your fields, and how you may be able to improve the types you have.
General answer
If you want to marshal/unmarshal into a struct/type you made, there's a simple rule to keep in mind:
json.Marshal
and json.Unmarshal
can only access exported fields. An exported field have Capitalised identifiers. Your Index
fieldin the ElkBulkInsert
is a slice of an anonymous struct, which has no exported fields (_Index
and _Id
start with an underscore).
Because you're using the json:"_index"
tags anyway, the field name itself doesn't have to even resemble the fields of the JSON itself. It's obviously preferable they do in most cases, but it's not required. As an aside: you have a field called Url
. It's generally considered better form to follow the standards WRT initialisms, and rename that field to URL
:
Words in names that are initialisms or acronyms (e.g. "URL" or "NATO") have a consistent case. For example, "URL" should appear as "URL" or "url" (as in "urlPony", or "URLPony"), never as "Url". Here's an example: ServeHTTP not ServeHttp.
This rule also applies to "ID" when it is short for "identifier," so write "appID" instead of "appId".
Code generated by the protocol buffer compiler is exempt from this rule. Human-written code is held to a higher standard than machine-written code.
With that being said, simply changing the types to this will work:
type ElkBulkInsert struct {
Index []struct {
Index string `json:"_index"`
ID string `json:"_id"`
} `json:"index"`
}
type ElkBulIsertUrl struct {
URL string `json:"url"`
}
Of course, this implies the data for ElkBulkInsert
looks something like:
{
"index": [
{
"_index": "foo",
"_id": "bar"
},
{
"_index": "fizz",
"_id": "buzz"
}
]
}
When you want to set values for a structure like this, I generally find it easier to shy away from using anonymous struct fields like the one you have in your Index
slice, and use something like:
type ElkInsertIndex struct {
ID string `json:"_id"`
Index string `json:"_index"`
}
type ElkBulkInsert struct {
Index []ElkInsertIndex `json:"index"`
}
This makes it a lot easier to populate the slice:
bulk := ElkBulkInsert{
Index: make([]ElkInsertIndex, 0, 10), // as an example
}
for i := 0; i < 10; i++ {
bulk.Index = append(bulk.Index, ElkInsertIndex{
ID: fmt.Sprintf("%d", i),
Index: fmt.Sprintf("Idx@%d", i), // wherever these values come from
})
}
Even easier (for instance when writing fixtures or unit tests) is to create a pre-populated literal:
data := ElkBulkInsert{
Index: []ElkInsertIndex{
{
ID: "foo",
Index: "bar",
},
{
ID: "fizz",
Index: "buzz",
},
},
}
With your current type, using the anonymous struct type, you can still do the same thing, but it looks messier, and requires more maintenance: you have to repeat the type:
data := ElkBulkInsert{
Index: []struct{
ID string `json:"_id"`
Index string `json:"_index"`
} {
ID: "foo",
Index: "bar",
},
{ // you can omit the field names if you know the order, and initialise all of them
"fizz",
"buzz",
},
}
Omitting field names when initialising in possible in both cases, but I'd advise against it. As fields get added/renamed/moved around over time, maintaining this mess becomes a nightmare. That's also why I'd strongly suggest you use move away from the anonymous struct here. They can be useful in places, but when you're representing a known data-format that comes from, or is sent to an external party (as JSON tends to do), I find it better to have all the relevant types named, labelled, documented somewhere. At the very least, you can add comments to each type, detailing what values it represents, and you can generate a nice, informative godoc
from it all.