如何将一个 JSON 对象数组转换为具有默认值的结构体数组(Go 语言)?

huangapple go评论88阅读模式
英文:

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 Datas 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.

huangapple
  • 本文由 发表于 2015年10月10日 13:05:04
  • 转载请务必保留本文链接:https://go.coder-hub.com/33050511.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定