Reputation: 4086
I have the following Go struct and JSON data:
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
}
Most of the time the value
of the JSON data is of type string
however sometimes it's of type number
.
If it's a number the json.Unmarshal
method returns an error like this:
json: cannot unmarshal number into Go struct field Entry.valueof type string
Is there an idiomatic and straightforward way to overcome such an issue in Go or I should implement a custom unmarshalling method for this case?
Upvotes: 0
Views: 1302
Reputation: 417767
You could use interface{}
for Entry.Value
, and the encoding/json
package will choose the proper type at runtime: string
for a JSON string, and float64
for a JSON number:
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)
}
This outputs (try it on the 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
Yes, then you will need a type assertion to get a typed value out from Entry.Value
.
Another option is to use json.Number
which may accommodate both strings and JSON numbers:
type Entry struct {
Timestamp string `json:"timestamp"`
Value json.Number `json:"value"`
}
Using this, the above example it outputs (this it on the 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"}
When using json.Number
, you may access its value using Number.String()
, or Number.Int64()
or Number.Float64()
which return an error if its value is not a number.
One thing to note here: if the JSON input is a string that contains a valid number, e.g. "12"
, then Number.Int64()
will not report error but parse it and return 12
. This is a difference compared to using intefface{}
(where Entry.Value
will remain string
).
Upvotes: 1
Reputation: 6345
Providing an alternative to icza's answer using a custom unmarshaller.
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)
}
}
Upvotes: 1