如何使用 go 的 json 标签将数字和字符串属性解组为字符串值

How to use go's json tag to unmarshal both numeric and string properties into a string value

我有以下 Go 结构和 JSON 数据:

type Entry struct {
    Timestamp string `json:"timestamp"`
    Value     string `json:"value"`
}
{
  "timestamp": "2020-01-01T00:00:00.000Z",
  "value": "a string" // but sometimes it's a number
}

大多数时候 JSON 数据的 valuestring 类型,但有时它是 number.

类型

如果它是一个数字 json.Unmarshal 方法 returns 像这样的错误:

json: cannot unmarshal number into Go struct field Entry.valueof type string

是否有一种惯用且直接的方法来克服 Go 中的此类问题,或者我应该针对这种情况实施自定义解组方法?

您可以将 interface{} 用于 Entry.Value,并且 encoding/json 程序包将在运行时选择正确的类型:string 用于 JSON 字符串, float64 JSON 号码:

type Entry struct {
    Timestamp string      `json:"timestamp"`
    Value     interface{} `json:"value"`
}

for _, s := range []string{
    `{  "timestamp": "2020-01-01T00:00:00.000Z",  "value": "a string" }`,
    `{  "timestamp": "2020-01-01T00:00:00.000Z",  "value": 12 }`,
} {

    var e Entry
    if err := json.Unmarshal([]byte(s), &e); err != nil {
        panic(err)
    }
    fmt.Printf("%#v %T\n", e, e.Value)
}

此输出(在 Go Playground 上尝试):

main.Entry{Timestamp:"2020-01-01T00:00:00.000Z", Value:"a string"} string
main.Entry{Timestamp:"2020-01-01T00:00:00.000Z", Value:12} float64

是的,那么您将需要 type assertionEntry.Value 中获取输入值。

另一种选择是使用 json.Number,它可以同时容纳字符串和 JSON 数字:

type Entry struct {
    Timestamp string      `json:"timestamp"`
    Value     json.Number `json:"value"`
}

使用这个,上面的例子输出(这个在Go Playground):

main.Entry{Timestamp:"2020-01-01T00:00:00.000Z", Value:"a string"}
main.Entry{Timestamp:"2020-01-01T00:00:00.000Z", Value:"12"}

当使用 json.Number 时,您可以使用 Number.String(), or Number.Int64() or Number.Float64() 访问它的值,如果它的值不是数字,return 会出错。

这里要注意一件事: 如果 JSON 输入是包含有效数字的字符串,例如"12",则Number.Int64()不报错而是解析,return12。这与使用 intefface{} 相比有所不同(其中 Entry.Value 将保持 string)。

使用自定义解组器提供 icza 答案的替代方法。

type Entry struct {
    Timestamp string     `json:"timestamp"`
    Value     EntryValue `json:"value"`
}

type EntryValue struct {
    string
}

func (e *EntryValue) UnmarshalJSON(data []byte) error {
    // Simplified check 
    e.string = string(bytes.Trim(data, `"`))
    return nil
}

func main() {

    for _, s := range []string{
        `{  "timestamp": "2020-01-01T00:00:00.000Z",  "value": "a string" }`,
        `{  "timestamp": "2020-01-01T00:00:00.000Z",  "value": 12 }`,
    } {

        var e Entry
        if err := json.Unmarshal([]byte(s), &e); err != nil {
            panic(err)
        }
        fmt.Printf("%#v\n", e)
    }
}