如何在 Go 中将 JSON 对象数组转换为具有默认值的结构数组?

Posted

技术标签:

【中文标题】如何在 Go 中将 JSON 对象数组转换为具有默认值的结构数组?【英文标题】:How do I turn an array of JSON objects into an array of structs with default values in Go? 【发布时间】:2016-01-08 03:00:14 【问题描述】:

我正在开发一个可以接收由 JSON 对象数组组成的 POST 的 Go API。 POST 的结构类似于:

[
  
    "name":"Las Vegas",
    "size":14
  ,
  
    "valid": false,
    "name":"Buffalo",
    "size":63
  
]  

假设我有以下结构:

type Data 
    Valid    bool
    Name     string
    Size     float64

我想创建一组Datas,并将Valid 设置为true,只要它实际上并未在JSON 中指定为false。如果我只做一个,我可以使用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["value"]; ok 
    data.Valid = v
   else 
    data.Valid = true
  
  id v, ok := item["name"]; ok 
    data.Name = v
  
  ...
  structs = append(structs, data)

return structs

现在我实际使用的结构有 14 个字段,其中一些具有我想要分配默认值的值,其他的可以留空,但所有这些都必须通过使用这种方法进行迭代。

有没有更好的办法?

【问题讨论】:

【参考方案1】:

您可以使用json.RawMessage 类型来延迟解组某些 JSON 文本值。如果您使用此类型,则 JSON 文本将存储在此中而无需解组(因此您可以稍后根据需要解组此片段)。

因此,在您的情况下,如果您尝试解组为此类RawMessage 的切片,则可以使用您在问题中链接的技术,即您可以遍历原始值切片(即 JSON 文本对于每个Data),创建一个Data 结构,其中的值作为缺失值的默认值,并将切片元素解组到这个准备好的结构中。就是这样。

看起来像这样:

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] = DataValid: 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 
    // Here create your Data with default values
    allData[i] = &DataValid: true
    if err := json.Unmarshal(v, allData[i]); err != nil 
        panic(err)
    

如果您想继续使用非指针,为了提高效率,您可以在切片元素本身中“准备”您希望的默认值,如下所示:

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)
    

【讨论】:

工作出色,谢谢。这可能是一个幼稚的问题,指针切片与结构切片相比哪个更有效?无论哪种方式,代码都在使用引用,不是吗? @jboschiero 在指针的情况下,当您为切片元素赋值时,它只复制一个指针(4 或 8 个字节)。如果切片包含非指针,则为切片元素(例如allData[i] = &DataValid: true)分配一个值会复制整个结构,如果你的结构很大(你说它有14个字段)可能很大,所以如果结构的大小是200字节,分配复制 200 个字节。【参考方案2】:

您可以通过在您的类型上提供UnmarshalJSON 方法来使其透明并自动工作,即使您的类型位于结构或切片中。

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

_Data 类型的存在只是为了让我们可以调用json.Unmarshal(j, &tmp) 并获得原始的未覆盖行为,而不是调用我们已经在其中的UnmarshalJSON 方法。我们可以使用您已经链接到的技巧在tmp 上设置默认值。然后在解组完成后,我们可以将tmp 转换为Data,因为毕竟Data_Data 确实是同一类型。

使用这种方法,您可以简单地

var structs []Data
err := json.Unmarshal(input, &structs)

(或同样使用json.Decoder)并让它按照您想要的方式工作。

【讨论】:

以上是关于如何在 Go 中将 JSON 对象数组转换为具有默认值的结构数组?的主要内容,如果未能解决你的问题,请参考以下文章

如何在android中将json对象从json数组转换为字符串数组

如何在 Javascript 中将表单数组转换为 JSON? (对象数组)?

如何在Nodejs中将json对象数组转换为String?

如何在PHP中将对象转换为特定格式的数组

在Javascript中将JSON字符串转换为JSON对象数组

如何在C#中将ExecuteReader转换为Json对象