I have the follow example code:
type Num struct {
X uint64 `json:"x,string"`
Y float64 `json:"y,string"`
}
Now, if I run the code
js := []byte(`{"x": "123", "y": "1.23"}`)
var n Num
err := json.Unmarshal(js, &n)
it will parse ok.
But if I change the JSON to
js := []byte(`{"x": 123, "y": 1.23}`)
it returns an error.
The result I can understand.
Now, my problem is how can I make it accept both string and uint64/float64?
You will need to define a custom type that implements the json.Unmarshaler
interface in such a way that the value can be either a string or a plain number.
The example below shows how to do it for a single type (uint64
); you will need to repeat the pattern for any other numerical types (Go Playground):
type Uint64 uint64
type Num struct {
X Uint64 `json:"x"`
}
func (u *Uint64) UnmarshalJSON(bs []byte) error {
str := string(bs) // Parse plain numbers directly.
if bs[0] == '"' && bs[len(bs)-1] == '"' {
// Unwrap the quotes from string numbers.
str = string(bs[1 : len(bs)-1])
}
x, err := strconv.ParseUint(str, 10, 64)
if err != nil {
return err
}
*u = Uint64(x)
return nil
}
func main() {
ss := []string{`{"x":"123"}`, `{"x":123}`}
var n Num
for _, s := range ss {
err := json.Unmarshal([]byte(s), &n)
fmt.Printf("OK: s=%-11s n=%#v err=%v
", s, n, err)
}
// OK: s={"x":"123"} n=main.Num{X:0x7b} err=<nil>
// OK: s={"x":123} n=main.Num{X:0x7b} err=<nil>
}
Building on @maerics answer, you can defer both cases to the usual json unmarshaler, which feels a bit more robust:
package main
import (
"encoding/json"
"errors"
"fmt"
)
type Uint64 uint64
type Num struct {
X Uint64 `json:"x"`
}
func (u *Uint64) UnmarshalJSON(bs []byte) error {
var i uint64
if err := json.Unmarshal(bs, &i); err == nil {
*u = Uint64(i)
return nil
}
var s string
if err := json.Unmarshal(bs, &s); err != nil {
return errors.New("expected a string or an integer")
}
if err := json.Unmarshal([]byte(s), &i); err != nil {
return err
}
*u = Uint64(i)
return nil
}
func main() {
ss := []string{`{"x":"123"}`, `{"x":123}`, `{"x":0.12}`}
var n Num
for _, s := range ss {
err := json.Unmarshal([]byte(s), &n)
fmt.Printf("OK: s=%-11s n=%#v err=%v
", s, n, err)
}
}
which gives
OK: s={"x":"123"} n=main.Num{X:0x7b} err=<nil>
OK: s={"x":123} n=main.Num{X:0x7b} err=<nil>
OK: s={"x":0.12} n=main.Num{X:0x7b} err=expected a string or an integer