【问题标题】:How to marshall and unmarshall into a struct with different types for the same value?如何将相同值编组和解组为具有不同类型的结构?
【发布时间】:2020-12-23 15:35:55
【问题描述】:

我正在调用一个外部 API,并将其解组为一个结构。

在响应中,大多数字段都是整数,但由于它是 json,因此有几种极端情况可以返回字符串,但仍然是有效/有用的信息: "NaN" , "N/A"

我的结构是这样的:

type Example struct {
  Field1 *int64 `json:"field_1,omitempty"`
  Field2 *int64 `json:"field_2,omitempty"`
  Field3 *int64 `json:"field_3,omitempty"`
}

我们有几个要求:

  • 如果 api 返回 NaN 或 N/A 我应该在 FE 中向我的用户显示一个错误,所以我想在“捕捉”错误的同时用 null 替换这些值 这就是我选择指针值的原因。

  • 如果没有返回值,则在重新编组时完全省略该值。

为了做到这一点,我试图用 JSON null 替换“NaN”值

 b = bytes.Replace(b, []byte("NaN"), []byte("null"), -1) ` 

但它不起作用,因为"null" 不等于null,这是第 1 个问题。

第二个问题是 omitempty 在重新编组时也不区分 nil、0 和空值。

所以重新编组也失败了。我知道这是 go that is being fixed 中的“常见”问题,但现在有解决办法吗?

因为如果我为“N/A”和“NaN”传递 nil 并使用 omitempty 它将删除它们。如果我通过 0 它将没有意义(商业明智,因为 0 具有“未初始化”以外的含义),如果我删除 Omitempty 它将具有 整个结构每次都编组(大量不必要的数据)并且无法区分 nil(NA / NaN)和 nil(无价值)。

最后一个选项是像这样构建自定义类型和编组/解组器:

type JSONint64 struct {
  value *int64
  error string
}

但这需要我每次都检查我的 json 响应中的每个数字,而事实上 NaN 和 N/A 是非常罕见的情况,并在前端增加了“复杂性”。

我假设这是一个常见问题,因为 JSON 是无类型的,这通常是如何解决的?

【问题讨论】:

  • 或者你可以解组到一个接口,然后检查值是否为整数
  • 但这会迫使我每次都为每个字段(有很多)断言,并且我认为会大大降低性能
  • 使用自定义解组器到底有什么问题? 是什么意思,这需要我每次都检查我的 json 响应中的每个数字“?由于您使用的是指针,您仍然必须检查它们是否有nil,并在您想对它们做一些有用的事情时取消引用它们,例如数学。还是我错过了什么?
  • 是的,目前还不清楚。我的意思是,为了验证我正在接收有效数据,我必须对我收到的所有值运行类型断言函数,而不是自然地将它们解组为 *int64 类型并处理少数边缘情况(如果有)错误。我从来没有收到 null 只是将它发送回我的前端,这是问题的第二部分,在 Go 中很难区分 nil 值和指针的空值。

标签: json go marshalling unmarshalling


【解决方案1】:

我会将Unmarshal 转换为map[string]interface{} 值,然后使用反射或类型断言来确定值的数据类型。

Unmarshal 将其中一项存储在接口值中:

bool, for JSON booleans
float64, for JSON numbers
string, for JSON strings
[]interface{}, for JSON arrays
map[string]interface{}, for JSON objects
nil for JSON null

像这样:

package main

import (
    "encoding/json"
    "fmt"
)

type Example struct {
    Field1 *int64 `json:"field_1,omitempty"`
    Field2 *int64 `json:"field_2,omitempty"`
    Field3 *int64 `json:"field_3,omitempty"`
}

func typeof(v interface{}) string {
    switch v.(type) {
    case float64:
        return "float64"
    case string:
        return "string"
    default:
        return "unknown"
    }
}

func main() {
    d := []byte(`{"field_1": "n/a", "field_2": 2 }"`)
    e := make(map[string]interface{})
    err := json.Unmarshal(d, &e)
    if err != nil {
        fmt.Printf("%v\n", err)
    }
    fmt.Printf("%+v\n", e)
    example := Example{}
    for k, v := range e {
        switch typeof(v) {
        case "float64":
            val := int64(v.(float64))
            switch k {
            case "field_1":
                example.Field1 = &val
            case "field_2":
                example.Field2 = &val
            case "field_3":
                example.Field3 = &val
            default:
                fmt.Printf("Unexpected field: %v\n", k)
            }

        default:
            // display error
        }
    }
    fmt.Printf("Example field_2: %d\n", *example.Field2)

}

【讨论】:

  • 但这不是很慢吗?我经常阅读不要在 Go 中使用类型断言。我试图避免它,因为 NaN 或 N/A 在我的用例中非常罕见。所以我会在非常罕见的边缘情况下运行一些非常慢的东西。这就是为什么我正在考虑使用类似 Byte.replace() 的方法将它们替换为 null
  • 如果这是一个问题,您可能需要编写一个基准测试来比较选项。
猜你喜欢
  • 1970-01-01
  • 2016-02-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-06-19
  • 1970-01-01
相关资源
最近更新 更多