diff --git a/canonical/json/decode_test.go b/canonical/json/decode_test.go index bbdd004..e4ed977 100644 --- a/canonical/json/decode_test.go +++ b/canonical/json/decode_test.go @@ -36,14 +36,14 @@ type V struct { var ifaceNumAsFloat64 = map[string]interface{}{ "k1": float64(1), "k2": "s", - "k3": []interface{}{float64(1), float64(2.0), float64(3e-3)}, + "k3": []interface{}{float64(1), float64(2.0), float64(3)}, "k4": map[string]interface{}{"kk1": "s", "kk2": float64(2)}, } var ifaceNumAsNumber = map[string]interface{}{ "k1": Number("1"), "k2": "s", - "k3": []interface{}{Number("1"), Number("2.0"), Number("3e-3")}, + "k3": []interface{}{Number("1"), Number("2.0"), Number("3")}, "k4": map[string]interface{}{"kk1": "s", "kk2": Number("2")}, } @@ -221,7 +221,7 @@ var unmarshalTests = []unmarshalTest{ // basic types {in: `true`, ptr: new(bool), out: true}, {in: `1`, ptr: new(int), out: 1}, - {in: `1.2`, ptr: new(float64), out: 1.2}, + {in: `1.0`, ptr: new(float64), out: 1.0}, {in: `-5`, ptr: new(int16), out: int16(-5)}, {in: `2`, ptr: new(Number), out: Number("2"), useNumber: true}, {in: `2`, ptr: new(Number), out: Number("2")}, @@ -236,13 +236,13 @@ var unmarshalTests = []unmarshalTest{ {in: `{"x": 1}`, ptr: new(tx), out: tx{}}, {in: `{"F1":1,"F2":2,"F3":3}`, ptr: new(V), out: V{F1: float64(1), F2: int32(2), F3: Number("3")}}, {in: `{"F1":1,"F2":2,"F3":3}`, ptr: new(V), out: V{F1: Number("1"), F2: int32(2), F3: Number("3")}, useNumber: true}, - {in: `{"k1":1,"k2":"s","k3":[1,2.0,3e-3],"k4":{"kk1":"s","kk2":2}}`, ptr: new(interface{}), out: ifaceNumAsFloat64}, - {in: `{"k1":1,"k2":"s","k3":[1,2.0,3e-3],"k4":{"kk1":"s","kk2":2}}`, ptr: new(interface{}), out: ifaceNumAsNumber, useNumber: true}, + {in: `{"k1":1,"k2":"s","k3":[1,2.0,3],"k4":{"kk1":"s","kk2":2}}`, ptr: new(interface{}), out: ifaceNumAsFloat64}, + {in: `{"k1":1,"k2":"s","k3":[1,2.0,3],"k4":{"kk1":"s","kk2":2}}`, ptr: new(interface{}), out: ifaceNumAsNumber, useNumber: true}, // raw values with whitespace {in: "\n true ", ptr: new(bool), out: true}, {in: "\t 1 ", ptr: new(int), out: 1}, - {in: "\r 1.2 ", ptr: new(float64), out: 1.2}, + {in: "\r 1.0 ", ptr: new(float64), out: 1.0}, {in: "\t -5 \n", ptr: new(int16), out: int16(-5)}, {in: "\t \"a\\u1234\" \n", ptr: new(string), out: "a\u1234"}, @@ -836,8 +836,8 @@ var allValue = All{ Uint32: 10, Uint64: 11, Uintptr: 12, - Float32: 14.1, - Float64: 15.1, + Float32: 14, + Float64: 15, Foo: "foo", Foo2: "foo2", IntStr: 42, @@ -858,7 +858,7 @@ var allValue = All{ ByteSlice: []byte{27, 28, 29}, Small: Small{Tag: "tag30"}, PSmall: &Small{Tag: "tag31"}, - Interface: 5.2, + Interface: 5.0, } var pallValue = All{ @@ -898,8 +898,8 @@ var allValueIndent = `{ "Uint32": 10, "Uint64": 11, "Uintptr": 12, - "Float32": 14.1, - "Float64": 15.1, + "Float32": 14, + "Float64": 15, "bar": "foo", "bar2": "foo2", "IntStr": "42", @@ -971,7 +971,7 @@ var allValueIndent = `{ "Tag": "tag31" }, "PPSmall": null, - "Interface": 5.2, + "Interface": 5, "PInterface": null }` @@ -1007,8 +1007,8 @@ var pallValueIndent = `{ "PUint32": 10, "PUint64": 11, "PUintptr": 12, - "PFloat32": 14.1, - "PFloat64": 15.1, + "PFloat32": 14, + "PFloat64": 15, "String": "", "PString": "16", "Map": null, @@ -1060,7 +1060,7 @@ var pallValueIndent = `{ "Tag": "tag31" }, "Interface": null, - "PInterface": 5.2 + "PInterface": 5 }` var pallValueCompact = strings.Map(noSpace, pallValueIndent) @@ -1209,8 +1209,8 @@ func TestUnmarshalNulls(t *testing.T) { Uint16: 9, Uint32: 10, Uint64: 11, - Float32: 12.1, - Float64: 13.1, + Float32: 12.0, + Float64: 13.0, String: "14"} err := Unmarshal(jsonData, &nulls) @@ -1219,7 +1219,7 @@ func TestUnmarshalNulls(t *testing.T) { } if !nulls.Bool || nulls.Int != 2 || nulls.Int8 != 3 || nulls.Int16 != 4 || nulls.Int32 != 5 || nulls.Int64 != 6 || nulls.Uint != 7 || nulls.Uint8 != 8 || nulls.Uint16 != 9 || nulls.Uint32 != 10 || nulls.Uint64 != 11 || - nulls.Float32 != 12.1 || nulls.Float64 != 13.1 || nulls.String != "14" { + nulls.Float32 != 12.0 || nulls.Float64 != 13.0 || nulls.String != "14" { t.Errorf("Unmarshal of null values affected primitives") } @@ -1367,13 +1367,13 @@ func TestPrefilled(t *testing.T) { }{ { in: `{"X": 1, "Y": 2}`, - ptr: &XYZ{X: float32(3), Y: int16(4), Z: 1.5}, - out: &XYZ{X: float64(1), Y: float64(2), Z: 1.5}, + ptr: &XYZ{X: float32(3), Y: int16(4), Z: 1}, + out: &XYZ{X: float64(1), Y: float64(2), Z: 1}, }, { in: `{"X": 1, "Y": 2}`, - ptr: ptrToMap(map[string]interface{}{"X": float32(3), "Y": int16(4), "Z": 1.5}), - out: ptrToMap(map[string]interface{}{"X": float64(1), "Y": float64(2), "Z": 1.5}), + ptr: ptrToMap(map[string]interface{}{"X": float32(3), "Y": int16(4), "Z": 1}), + out: ptrToMap(map[string]interface{}{"X": float64(1), "Y": float64(2), "Z": 1}), }, } diff --git a/canonical/json/encode.go b/canonical/json/encode.go index 7793733..f5dcb62 100644 --- a/canonical/json/encode.go +++ b/canonical/json/encode.go @@ -14,6 +14,7 @@ import ( "bytes" "encoding" "encoding/base64" + "fmt" "math" "reflect" "runtime" @@ -519,8 +520,11 @@ type floatEncoder int // number of bits func (bits floatEncoder) encode(e *encodeState, v reflect.Value, quoted bool) { f := v.Float() - if math.IsInf(f, 0) || math.IsNaN(f) { - e.error(&UnsupportedValueError{v, strconv.FormatFloat(f, 'g', -1, int(bits))}) + if math.IsInf(f, 0) || math.IsNaN(f) || math.Floor(f) != f { + e.error(&UnsupportedValueError{ + v, + fmt.Sprintf("floating point number, %s", strconv.FormatFloat(f, 'g', -1, int(bits))), + }) } b := strconv.AppendFloat(e.scratch[:0], f, 'g', -1, int(bits)) if quoted { diff --git a/canonical/json/encode_test.go b/canonical/json/encode_test.go index d497bd9..bcb1cf5 100644 --- a/canonical/json/encode_test.go +++ b/canonical/json/encode_test.go @@ -532,34 +532,43 @@ func TestEncodeString(t *testing.T) { } type ObjEnc struct { - A int - B OrderedObject - C interface{} + A int + B OrderedObject + C interface{} } var orderedObjectTests = []struct { - in interface{} - out string + in interface{} + out string }{ - {OrderedObject{}, `{}`}, - {OrderedObject{Member{"A", []int{1, 2, 3}}, Member{"B", 23}, Member{"C", "C"}}, `{"A":[1,2,3],"B":23,"C":"C"}`}, - {ObjEnc{A: 234, B: OrderedObject{Member{"K", "V"}, Member{"V", 4}}}, `{"A":234,"B":{"K":"V","V":4},"C":null}`}, - {ObjEnc{A: 234, B: OrderedObject{}, C: OrderedObject{{"A", 0}}}, `{"A":234,"B":{},"C":{"A":0}}`}, - {[]OrderedObject{{{"A", "Ay"}, {"B", "Bee"}}, {{"A", "Nay"}}}, `[{"A":"Ay","B":"Bee"},{"A":"Nay"}]`}, - {map[string]OrderedObject{"A": {{"A", "Ay"}, {"B", "Bee"}}}, `{"A":{"A":"Ay","B":"Bee"}}`}, - {map[string]interface{}{"A": OrderedObject{{"A", "Ay"}, {"B", "Bee"}}}, `{"A":{"A":"Ay","B":"Bee"}}`}, + {OrderedObject{}, `{}`}, + {OrderedObject{Member{"A", []int{1, 2, 3}}, Member{"B", 23}, Member{"C", "C"}}, `{"A":[1,2,3],"B":23,"C":"C"}`}, + {ObjEnc{A: 234, B: OrderedObject{Member{"K", "V"}, Member{"V", 4}}}, `{"A":234,"B":{"K":"V","V":4},"C":null}`}, + {ObjEnc{A: 234, B: OrderedObject{}, C: OrderedObject{{"A", 0}}}, `{"A":234,"B":{},"C":{"A":0}}`}, + {[]OrderedObject{{{"A", "Ay"}, {"B", "Bee"}}, {{"A", "Nay"}}}, `[{"A":"Ay","B":"Bee"},{"A":"Nay"}]`}, + {map[string]OrderedObject{"A": {{"A", "Ay"}, {"B", "Bee"}}}, `{"A":{"A":"Ay","B":"Bee"}}`}, + {map[string]interface{}{"A": OrderedObject{{"A", "Ay"}, {"B", "Bee"}}}, `{"A":{"A":"Ay","B":"Bee"}}`}, } func TestEncodeOrderedObject(t *testing.T) { - for i, o := range orderedObjectTests { - d, err := Marshal(o.in) - if err != nil { - t.Errorf("Unexpected error %v", err) - continue - } - ds := string(d) - if o.out != ds { - t.Errorf("#%d expected '%v', was '%v'", i, o.out, ds) - } - } + for i, o := range orderedObjectTests { + d, err := Marshal(o.in) + if err != nil { + t.Errorf("Unexpected error %v", err) + continue + } + ds := string(d) + if o.out != ds { + t.Errorf("#%d expected '%v', was '%v'", i, o.out, ds) + } + } +} + +func TestFloatError(t *testing.T) { + input := struct{ A float64 }{1.1} + + _, err := Marshal(input) + if err == nil { + t.Errorf("want float error, got nil") + } } diff --git a/canonical/json/scanner_test.go b/canonical/json/scanner_test.go index 7880342..fee7b50 100644 --- a/canonical/json/scanner_test.go +++ b/canonical/json/scanner_test.go @@ -264,7 +264,7 @@ func genValue(n int) interface{} { case 0: return rand.Intn(2) == 0 case 1: - return rand.NormFloat64() + return math.Floor(rand.NormFloat64()) case 2: return genString(30) } diff --git a/canonical/json/stream_test.go b/canonical/json/stream_test.go index b562e87..66cd0b1 100644 --- a/canonical/json/stream_test.go +++ b/canonical/json/stream_test.go @@ -16,24 +16,24 @@ import ( // Test values for the stream test. // One of each JSON kind. var streamTest = []interface{}{ - 0.1, + 1.0, "hello", nil, true, false, []interface{}{"a", "b", "c"}, map[string]interface{}{"K": "Kelvin", "ß": "long s"}, - 3.14, // another value to make sure something can follow map + 3.0, // another value to make sure something can follow map } -var streamEncoded = `0.1 +var streamEncoded = `1 "hello" null true false ["a","b","c"] {"ß":"long s","K":"Kelvin"} -3.14 +3 ` func TestEncoder(t *testing.T) { @@ -129,7 +129,7 @@ func TestRawMessage(t *testing.T) { Y float32 } const raw = `["\u0056",null]` - const msg = `{"X":0.1,"Id":["\u0056",null],"Y":0.2}` + const msg = `{"X":1,"Id":["\u0056",null],"Y":2}` err := Unmarshal([]byte(msg), &data) if err != nil { t.Fatalf("Unmarshal: %v", err) @@ -154,7 +154,7 @@ func TestNullRawMessage(t *testing.T) { Y float32 } data.Id = new(RawMessage) - const msg = `{"X":0.1,"Id":null,"Y":0.2}` + const msg = `{"X":1,"Id":null,"Y":2}` err := Unmarshal([]byte(msg), &data) if err != nil { t.Fatalf("Unmarshal: %v", err)