如何使用 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 数据的 value
是 string
类型,但有时它是 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 assertion 从 Entry.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)
}
}
我有以下 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 数据的 value
是 string
类型,但有时它是 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 assertion 从 Entry.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)
}
}