英文:
How do I turn an array of JSON objects into an array of structs with default values in Go?
问题
我正在开发一个Go API,可以接收由JSON对象数组组成的POST请求。POST的结构大致如下:
[
{
"name":"Las Vegas",
"size":14
},
{
"valid": false,
"name":"Buffalo",
"size":63
}
]
假设我有以下结构体:
type Data struct {
Valid bool
Name string
Size float64
}
我想创建一堆Data
对象,其中Valid
属性在JSON中未明确指定为false
时,将其设置为true
。如果只有一个对象,我可以使用https://stackoverflow.com/questions/30445479/how-to-specify-default-values-when-parsing-json-in-go中的方法,但是对于未知数量的对象,我只能想到以下方法:
var allMap []map[string]interface{}
var structs []Data
for _, item := range allMap {
var data Data
var v interface{}
var ok bool
if v, ok := item["valid"]; ok {
data.Valid = v
} else {
data.Valid = true
}
if v, ok := item["name"]; ok {
data.Name = v
}
...
structs = append(structs, data)
}
return structs
目前,我正在处理的结构体有14个字段,其中一些字段的值我想要指定默认值,其他字段则可以留空,但是所有字段都必须使用这种方法进行迭代。
有没有更好的方法?
英文:
I'm working on a Go API that can receive POSTs consisting of a JSON array of objects. The structure of the POST will look something like:
[
{
"name":"Las Vegas",
"size":14
},
{
"valid": false,
"name":"Buffalo",
"size":63
}
]
Let's say I have the following struct:
type Data {
Valid bool
Name string
Size float64
}
I want to create a bunch of Data
s with Valid
set to true
anytime it's not actually specified in the JSON as false
. If I were doing a single one I could use https://stackoverflow.com/questions/30445479/how-to-specify-default-values-when-parsing-json-in-go, but for doing an unknown number of them the only thing I've been able to come up with is something like:
var allMap []map[string]interface{}
var structs []Data
for _, item := range allMap {
var data Data
var v interface{}
var ok bool
if v, ok := item["value"]; ok {
data.Valid = v
} else {
data.Valid = true
}
id v, ok := item["name"]; ok {
data.Name = v
}
...
structs = append(structs, data)
}
return structs
Right now the struct I'm actually working with has 14 fields, some of them have values I want to assign defaults, others are fine to leave blank, but all of them have to be iterated through using this approach.
Is there a better way?
答案1
得分: 3
你可以使用json.RawMessage
类型来延迟解析一些JSON文本值。如果使用这个类型,那么JSON文本将被存储在其中而不进行解析(这样你可以在以后根据需要解析这个片段)。
所以在你的情况下,如果你尝试将其解析为一个RawMessage
切片,你可以使用你在问题中提到的技术,即遍历原始值的切片(每个Data
的JSON文本),创建一个具有你想要的默认值的Data
结构,并将切片元素解析到这个准备好的结构中。就是这样。
代码如下:
allJson := []json.RawMessage{}
if err := json.Unmarshal(src, &allJson); err != nil {
panic(err)
}
allData := make([]Data, len(allJson))
for i, v := range allJson {
// 在这里使用默认值创建你的Data结构体
allData[i] = Data{Valid: true}
if err := json.Unmarshal(v, &allData[i]); err != nil {
panic(err)
}
}
你可以在Go Playground上尝试运行。
注意/变体
为了提高效率(避免复制结构体),你还可以将allData
设置为上面示例中的指针切片,代码如下:
allData := make([]*Data, len(allJson))
for i, v := range allJson {
// 在这里使用默认值创建你的Data结构体
allData[i] = &Data{Valid: true}
if err := json.Unmarshal(v, allData[i]); err != nil {
panic(err)
}
}
如果你想继续使用非指针类型,为了提高效率,你可以在切片元素本身中“准备”你希望的默认值,代码如下:
allData := make([]Data, len(allJson))
for i, v := range allJson {
// 在这里设置切片元素的默认值
// 只设置与零值不同的值:
allData[i].Valid = true
if err := json.Unmarshal(v, &allData[i]); err != nil {
panic(err)
}
}
英文:
You can use the json.RawMessage
type to defer unmarshaling some JSON text value. If you use this type, then the JSON text will be stored in this without unmarshaling (so you can unmarshal this fragment later on as you wish).
So in your case if you try to unmarshal into a slice of such RawMessage
, you can use the technique what you linked in your question, that is you can iterate over the slice of raw values (which are the JSON text for each Data
), create a Data
struct with values you want as defaults for missing values, and unmarshal a slice element into this prepared struct. That's all.
It looks like this:
allJson := []json.RawMessage{}
if err := json.Unmarshal(src, &allJson); err != nil {
panic(err)
}
allData := make([]Data, len(allJson))
for i, v := range allJson {
// Here create your Data with default values
allData[i] = Data{Valid: true}
if err := json.Unmarshal(v, &allData[i]); err != nil {
panic(err)
}
}
Try it on the Go Playground.
Notes / Variants
For efficiency (to avoid copying structs), you can also make the allData
to be a slice of pointers in the above example, which would look like this:
allData := make([]*Data, len(allJson))
for i, v := range allJson {
// Here create your Data with default values
allData[i] = &Data{Valid: true}
if err := json.Unmarshal(v, allData[i]); err != nil {
panic(err)
}
}
If you want to keep using non-pointers, for efficiency you can "prepare" your wished default values in the slice elements itself, which would look like this:
allData := make([]Data, len(allJson))
for i, v := range allJson {
// Here set your default values in the slice elements
// Only set those which defer from the zero values:
allData[i].Valid = true
if err := json.Unmarshal(v, &allData[i]); err != nil {
panic(err)
}
}
答案2
得分: 1
你可以通过在类型上提供一个UnmarshalJSON
方法来实现一个很好的技巧,使其在结构体或切片中找到该类型时自动透明地工作。
func (d *Data) UnmarshalJSON(j []byte) error {
type _Data Data // 用于避免在UnmarshalJSON中出现无限递归的虚拟类型
tmp := _Data{ // 在这里设置默认值
Valid: true,
}
err := json.Unmarshal(j, &tmp)
if err != nil {
return err
}
*d = Data(tmp)
return nil
}
_Data
类型的存在仅仅是为了我们可以调用json.Unmarshal(j, &tmp)
并获得原始的未覆盖行为,而不是调用我们已经在其中的UnmarshalJSON
方法。我们可以使用你已经提供的技巧在tmp
上设置默认值。然后在解组完成后,我们可以将tmp
强制转换为Data
,因为毕竟Data
和_Data
实际上是相同的类型。
有了这个方法,你可以简单地这样做:
var structs []Data
err := json.Unmarshal(input, &structs)
(或者使用json.Decoder
同样的方式),它会按照你期望的方式正常工作。
英文:
You can do a good trick by providing an UnmarshalJSON
method on your type to make it transparent and work automatically even if your type is found within structs or slices.
func (d *Data) UnmarshalJSON(j []byte) error {
type _Data Data // Dummy type to avoid infinite recursion in UnmarshalJSON
tmp := _Data{ // Set defaults here
Valid: true,
}
err := json.Unmarshal(j, &tmp)
if err != nil {
return err
}
*d = Data(tmp)
return nil
}
The type _Data
exists simply so that we can call json.Unmarshal(j, &tmp)
and get the original un-overridden behavior, instead of calling the UnmarshalJSON
method that we're already in the middle of. We can set default values on tmp
using the trick that you already linked to. And then after the unmarshalling is done, we can just cast tmp
to Data
because after all Data
and _Data
are really the same type.
Given this method you can simply
var structs []Data
err := json.Unmarshal(input, &structs)
(or likewise with a json.Decoder
) and have it work just the way you want.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论