Handle json.Number values.
In order to prevent the JSON decoder from converting all values to
`float64`, which causes a number of problems, one must set `UseNumber` on
a JSON decoder which tells it to unmarshal as `json.Number` values.
These can then be converted to ints, floats, or used as strings.
However, although setting this value is very common, mapstructure cannot
currently decode from a `map[string]interface{}` containing a
`json.Number` to a struct with those values represented as int or float.
This adds code and test cases to correctly decode a json.Number into an
int or float as expected. It does not decode into uint, although this
could be handled, since json.Number does not natively decode into an
unsigned int.
diff --git a/mapstructure.go b/mapstructure.go
index 4490521..a554e79 100644
--- a/mapstructure.go
+++ b/mapstructure.go
@@ -8,6 +8,7 @@
package mapstructure
import (
+ "encoding/json"
"errors"
"fmt"
"reflect"
@@ -306,6 +307,7 @@
func (d *Decoder) decodeInt(name string, data interface{}, val reflect.Value) error {
dataVal := reflect.ValueOf(data)
dataKind := getKind(dataVal)
+ dataType := dataVal.Type()
switch {
case dataKind == reflect.Int:
@@ -327,6 +329,14 @@
} else {
return fmt.Errorf("cannot parse '%s' as int: %s", name, err)
}
+ case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number":
+ jn := data.(json.Number)
+ i, err := jn.Int64()
+ if err != nil {
+ return fmt.Errorf(
+ "error decoding json.Number into %s: %s", name, err)
+ }
+ val.SetInt(i)
default:
return fmt.Errorf(
"'%s' expected type '%s', got unconvertible type '%s'",
@@ -413,6 +423,7 @@
func (d *Decoder) decodeFloat(name string, data interface{}, val reflect.Value) error {
dataVal := reflect.ValueOf(data)
dataKind := getKind(dataVal)
+ dataType := dataVal.Type()
switch {
case dataKind == reflect.Int:
@@ -434,6 +445,14 @@
} else {
return fmt.Errorf("cannot parse '%s' as float: %s", name, err)
}
+ case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number":
+ jn := data.(json.Number)
+ i, err := jn.Float64()
+ if err != nil {
+ return fmt.Errorf(
+ "error decoding json.Number into %s: %s", name, err)
+ }
+ val.SetFloat(i)
default:
return fmt.Errorf(
"'%s' expected type '%s', got unconvertible type '%s'",
diff --git a/mapstructure_test.go b/mapstructure_test.go
index 4d40ace..ea21924 100644
--- a/mapstructure_test.go
+++ b/mapstructure_test.go
@@ -1,6 +1,7 @@
package mapstructure
import (
+ "encoding/json"
"io"
"reflect"
"sort"
@@ -9,14 +10,17 @@
)
type Basic struct {
- Vstring string
- Vint int
- Vuint uint
- Vbool bool
- Vfloat float64
- Vextra string
- vsilent bool
- Vdata interface{}
+ Vstring string
+ Vint int
+ Vuint uint
+ Vbool bool
+ Vfloat float64
+ Vextra string
+ vsilent bool
+ Vdata interface{}
+ VjsonInt int
+ VjsonFloat float64
+ VjsonNumber json.Number
}
type BasicSquash struct {
@@ -109,13 +113,16 @@
t.Parallel()
input := map[string]interface{}{
- "vstring": "foo",
- "vint": 42,
- "Vuint": 42,
- "vbool": true,
- "Vfloat": 42.42,
- "vsilent": true,
- "vdata": 42,
+ "vstring": "foo",
+ "vint": 42,
+ "Vuint": 42,
+ "vbool": true,
+ "Vfloat": 42.42,
+ "vsilent": true,
+ "vdata": 42,
+ "vjsonInt": json.Number("1234"),
+ "vjsonFloat": json.Number("1234.5"),
+ "vjsonNumber": json.Number("1234.5"),
}
var result Basic
@@ -156,6 +163,18 @@
if result.Vdata != 42 {
t.Error("vdata should be valid")
}
+
+ if result.VjsonInt != 1234 {
+ t.Errorf("vjsonint value should be 1234: %#v", result.VjsonInt)
+ }
+
+ if result.VjsonFloat != 1234.5 {
+ t.Errorf("vjsonfloat value should be 1234.5: %#v", result.VjsonFloat)
+ }
+
+ if !reflect.DeepEqual(result.VjsonNumber, json.Number("1234.5")) {
+ t.Errorf("vjsonnumber value should be '1234.5': %T, %#v", result.VjsonNumber, result.VjsonNumber)
+ }
}
func TestBasic_IntWithFloat(t *testing.T) {