-2

I wanted to clarify how to set values for

type ElkBulkInsert struct {
    Index []struct {
        _Index string `json:"_index"`
        _Id    string `json:"_id"`
    } `json:"index"`
}

to make json.Marshall there were no problems for the usual structure

package main
import (
    "encoding/json"
    "fmt"
)
type ElkBulkInsert struct {
    Index []struct {
        _Index string `json:"_index"`
        _Id    string `json:"_id"`
    } `json:"index"`
}
type ElkBulIsertUrl struct {
    Url string `json:"url"`
}
func main() {
    sf := ElkBulIsertUrl{
        Url: "http://mail.ru",
    }
    dd, _ := json.Marshal(sf)
    fmt.Println(string(dd))
}
Shota
  • 1
  • 3
  • 2
    what's your question? – blackgreen Feb 02 '22 at 09:15
  • 1
    Duplicate: [Initialize nested struct definition](https://stackoverflow.com/questions/26866879/initialize-nested-struct-definition). Advice: do not use anonymous struct types, give the inner struct a proper type name, then it will be much easier to work with it. – rustyx Feb 02 '22 at 09:36
  • 3
    Note that `_Index` and `_Id` are both unexported fields. You can't marshal/unmarshal them. Please read the spec on [Exported identifiers](https://go.dev/ref/spec#Exported_identifiers). – mkopriva Feb 02 '22 at 09:44

1 Answers1

1

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.

Dharman
  • 30,962
  • 25
  • 85
  • 135
Elias Van Ootegem
  • 74,482
  • 9
  • 111
  • 149