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\n", 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
float64, then convert what you want?"y": 1.23with"y": "1.23"then you would always handle a string internal, but your api can handle both.interface{}