英文:
GO is a complex nested structure
问题
我想澄清一下如何设置以下结构体的值,以便进行json.Marshal操作。对于常规结构体没有问题。
type ElkBulkInsert struct {
Index []struct {
_Index string `json:"_index"`
_Id string `json:"_id"`
} `json:"index"`
}
以下是一个示例程序:
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))
}
请注意,结构体字段的标签应该是json:"字段名"
,而不是json:"字段名"
。
英文:
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))
}
答案1
得分: 1
这里你的问题并不清楚。你是在问为什么 JSON 输出与你的预期不符吗?你是否不确定如何初始化/设置类型为 []struct{...}
的 Index
字段?
由于问题不够清晰,我将尝试解释为什么你的 JSON 输出可能会缺少字段(或者为什么不是所有字段都被填充),以及如何初始化字段和改进你所拥有的类型。
一般回答
如果你想要将数据编组/解组成你自己定义的结构体/类型,有一个简单的规则需要记住:
json.Marshal
和 json.Unmarshal
只能访问 公开的 字段。公开的字段是指首字母大写的标识符。你的 ElkBulkInsert
中的 Index
字段是一个匿名结构体的切片,该结构体没有公开的字段(_Index
和 _Id
以下划线开头)。<br>
因为你已经使用了 json:"_index"
标签,字段名本身甚至不必与 JSON 的字段名相似。在大多数情况下,最好是相似的,但这不是必需的。顺便说一句:你有一个名为 Url
的字段。根据 关于首字母缩略词的规范,更好的做法是将该字段重命名为 URL
:
> 名称中的首字母缩略词或首字母缩写(例如 "URL" 或 "NATO")应该保持一致的大小写。例如,"URL" 应该出现为 "URL" 或 "url"(例如 "urlPony" 或 "URLPony"),而不是 "Url"。以下是一个例子:ServeHTTP 而不是 ServeHttp。
>
> 当 "ID" 缩写为 "identifier" 时,应该写成 "appID" 而不是 "appId"。
>
> 由协议缓冲区编译器生成的代码不受此规则的约束。人工编写的代码比机器生成的代码要求更高。
有了这些说法,只需将类型更改为以下内容即可:
type ElkBulkInsert struct {
Index []struct {
Index string `json:"_index"`
ID string `json:"_id"`
} `json:"index"`
}
type ElkBulIsertUrl struct {
URL string `json:"url"`
}
当然,这意味着 ElkBulkInsert
的数据看起来应该是这样的:
{
"index": [
{
"_index": "foo",
"_id": "bar"
},
{
"_index": "fizz",
"_id": "buzz"
}
]
}
当你想要设置这样的结构体的值时,我通常发现避免使用你在 Index
切片中使用的匿名结构体字段更容易,而是使用类似这样的方式:
type ElkInsertIndex struct {
ID string `json:"_id"`
Index string `json:"_index"`
}
type ElkBulkInsert struct {
Index []ElkInsertIndex `json:"index"`
}
这样可以更容易地填充切片:
bulk := ElkBulkInsert{
Index: make([]ElkInsertIndex, 0, 10), // 作为示例
}
for i := 0; i < 10; i++ {
bulk.Index = append(bulk.Index, ElkInsertIndex{
ID: fmt.Sprintf("%d", i),
Index: fmt.Sprintf("Idx@%d", i), // 这些值来自于哪里
})
}
甚至更简单的方式(例如在编写固定数据或单元测试时)是创建一个预填充的字面量:
data := ElkBulkInsert{
Index: []ElkInsertIndex{
{
ID: "foo",
Index: "bar",
},
{
ID: "fizz",
Index: "buzz",
},
},
}
使用你当前的类型,使用匿名结构体类型仍然可以做到相同的事情,但看起来更乱,并且需要更多的维护:你必须重复类型:
data := ElkBulkInsert{
Index: []struct{
ID string `json:"_id"`
Index string `json:"_index"`
} {
ID: "foo",
Index: "bar",
},
{ // 如果你知道顺序,可以省略字段名,并初始化所有字段
"fizz",
"buzz",
},
}
在这两种情况下,都可以省略字段名进行初始化,但我建议不要这样做。随着时间的推移,当字段被添加/重命名/移动时,维护这种混乱的代码变得非常困难。这也是为什么我强烈建议你在这里摆脱匿名结构体的原因。它们在某些情况下可能很有用,但当你表示来自或发送给外部方的已知数据格式时(如 JSON 常常做的那样),我发现最好将所有相关类型命名、标记、在某个地方进行文档化。至少,你可以为每个类型添加注释,详细说明它表示的值,并且可以从中生成一个漂亮、信息丰富的 godoc
。
英文:
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).<br>
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论