Type checking json values when unmarshaling in Go

Viewed 135

I'm working with an API that usually provides json string values but they will sometimes provide numbers instead. For example, 99% of the time it's like this:

{
    "Description": "Doorknob",
    "Amount": "3.25"
}

But for whatever reason, sometimes it's like this:

{
    "Description": "Lightbulb",
    "Amount": 4.70
}

Here is the struct I'm using which works in 99% of the time:

type Cart struct {
    Description string `json:"Description"`
    Amount      string `json:"Amount"`
}

But it'll break when they provide a numeric amount. What's the best way to type check when unmarshaling a struct?

playground: https://play.golang.org/p/S_gp2sQC5-A

2 Answers

For the general case you may use interface{} as described in Burak Serdar's answer.

For numbers specifically, there's the json.Number type: it accepts both JSON numbers and JSON strings, and it can "automatically" parse the number if it's given as string with Number.Int64() or Number.Float64(). No custom marshalers / unmarshalers are required.

type Cart struct {
    Description string      `json:"Description"`
    Amount      json.Number `json:"Amount"`
}

Testing it:

var (
    cart1 = []byte(`{
    "Description": "Doorknob",
    "Amount": "3.25"
}`)

    cart2 = []byte(`{
    "Description": "Lightbulb",
    "Amount": 4.70
}`)
)

func main() {
    var c1, c2 Cart
    if err := json.Unmarshal(cart1, &c1); err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", c1)
    if err := json.Unmarshal(cart2, &c2); err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", c2)
}

Output (try it on the Go Playground):

{Description:Doorknob Amount:3.25}
{Description:Lightbulb Amount:4.70}

If that field can be a string or int, you can use an interface{}, and then figure out the underlying value:

type Cart struct {
    Description string `json:"Description"`
    Amount      interface{} `json:"Amount"`
}

func (c Cart) GetAmount() (float64,error) {
  if d, ok:=c.Amount.(float64); ok {
     return d,nil
  }
  if s, ok:=c.Amount.(string); ok {
     return strconv.ParseFloat(s,64)
  }
  return 0, errors.New("Invalid amount")
}
Related