mirror of
https://github.com/tendermint/tendermint.git
synced 2026-09-25 17:34:36 +00:00
json: add Amino-compatible encoder/decoder (#4955)
Amino-compatible JSON encoder/decoder, including bug compatibility. Interface types must be registered via `json.RegisterType()`. Unlike Amino, this allows floats to be encoded/decoded. Partial fix for #4828, needs code migration.
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
package json_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tendermint/tendermint/libs/json"
|
||||
)
|
||||
|
||||
func TestMarshal(t *testing.T) {
|
||||
s := "string"
|
||||
sPtr := &s
|
||||
i64 := int64(64)
|
||||
ti := time.Date(2020, 6, 2, 18, 5, 13, 4346374, time.FixedZone("UTC+2", 2*60*60))
|
||||
car := &Car{Wheels: 4}
|
||||
boat := Boat{Sail: true}
|
||||
|
||||
testcases := map[string]struct {
|
||||
value interface{}
|
||||
output string
|
||||
}{
|
||||
"nil": {nil, `null`},
|
||||
"string": {"foo", `"foo"`},
|
||||
"float32": {float32(3.14), `3.14`},
|
||||
"float32 neg": {float32(-3.14), `-3.14`},
|
||||
"float64": {float64(3.14), `3.14`},
|
||||
"float64 neg": {float64(-3.14), `-3.14`},
|
||||
"int32": {int32(32), `32`},
|
||||
"int64": {int64(64), `"64"`},
|
||||
"int64 neg": {int64(-64), `"-64"`},
|
||||
"int64 ptr": {&i64, `"64"`},
|
||||
"uint64": {uint64(64), `"64"`},
|
||||
"time": {ti, `"2020-06-02T16:05:13.004346374Z"`},
|
||||
"time empty": {time.Time{}, `"0001-01-01T00:00:00Z"`},
|
||||
"time ptr": {&ti, `"2020-06-02T16:05:13.004346374Z"`},
|
||||
"customptr": {CustomPtr{Value: "x"}, `{"Value":"x"}`}, // same as encoding/json
|
||||
"customptr ptr": {&CustomPtr{Value: "x"}, `"custom"`},
|
||||
"customvalue": {CustomValue{Value: "x"}, `"custom"`},
|
||||
"customvalue ptr": {&CustomValue{Value: "x"}, `"custom"`},
|
||||
"slice nil": {[]int(nil), `null`},
|
||||
"slice empty": {[]int{}, `[]`},
|
||||
"slice bytes": {[]byte{1, 2, 3}, `"AQID"`},
|
||||
"slice int64": {[]int64{1, 2, 3}, `["1","2","3"]`},
|
||||
"slice int64 ptr": {[]*int64{&i64, nil}, `["64",null]`},
|
||||
"array bytes": {[3]byte{1, 2, 3}, `"AQID"`},
|
||||
"array int64": {[3]int64{1, 2, 3}, `["1","2","3"]`},
|
||||
"map nil": {map[string]int64(nil), `{}`}, // retain Amino compatibility
|
||||
"map empty": {map[string]int64{}, `{}`},
|
||||
"map int64": {map[string]int64{"a": 1, "b": 2, "c": 3}, `{"a":"1","b":"2","c":"3"}`},
|
||||
"car": {car, `{"type":"vehicle/car","value":{"Wheels":4}}`},
|
||||
"car value": {*car, `{"type":"vehicle/car","value":{"Wheels":4}}`},
|
||||
"car iface": {Vehicle(car), `{"type":"vehicle/car","value":{"Wheels":4}}`},
|
||||
"car nil": {(*Car)(nil), `null`},
|
||||
"boat": {boat, `{"type":"vehicle/boat","value":{"Sail":true}}`},
|
||||
"boat ptr": {&boat, `{"type":"vehicle/boat","value":{"Sail":true}}`},
|
||||
"boat iface": {Vehicle(boat), `{"type":"vehicle/boat","value":{"Sail":true}}`},
|
||||
"key public": {PublicKey{1, 2, 3, 4, 5, 6, 7, 8}, `{"type":"key/public","value":"AQIDBAUGBwg="}`},
|
||||
"tags": {
|
||||
Tags{JSONName: "name", OmitEmpty: "foo", Hidden: "bar", Tags: &Tags{JSONName: "child"}},
|
||||
`{"name":"name","OmitEmpty":"foo","tags":{"name":"child"}}`,
|
||||
},
|
||||
"tags empty": {Tags{}, `{"name":""}`},
|
||||
// The encoding of the Car and Boat fields do not have type wrappers, even though they get
|
||||
// type wrappers when encoded directly (see "car" and "boat" tests). This is to retain the
|
||||
// same behavior as Amino. If the field was a Vehicle interface instead, it would get
|
||||
// type wrappers, as seen in the Vehicles field.
|
||||
"struct": {
|
||||
Struct{
|
||||
Bool: true, Float64: 3.14, Int32: 32, Int64: 64, Int64Ptr: &i64,
|
||||
String: "foo", StringPtrPtr: &sPtr, Bytes: []byte{1, 2, 3},
|
||||
Time: ti, Car: car, Boat: boat, Vehicles: []Vehicle{car, boat},
|
||||
Child: &Struct{Bool: false, String: "child"}, private: "private",
|
||||
},
|
||||
`{
|
||||
"Bool":true, "Float64":3.14, "Int32":32, "Int64":"64", "Int64Ptr":"64",
|
||||
"String":"foo", "StringPtrPtr": "string", "Bytes":"AQID",
|
||||
"Time":"2020-06-02T16:05:13.004346374Z",
|
||||
"Car":{"Wheels":4},
|
||||
"Boat":{"Sail":true},
|
||||
"Vehicles":[
|
||||
{"type":"vehicle/car","value":{"Wheels":4}},
|
||||
{"type":"vehicle/boat","value":{"Sail":true}}
|
||||
],
|
||||
"Child":{
|
||||
"Bool":false, "Float64":0, "Int32":0, "Int64":"0", "Int64Ptr":null,
|
||||
"String":"child", "StringPtrPtr":null, "Bytes":null,
|
||||
"Time":"0001-01-01T00:00:00Z",
|
||||
"Car":null, "Boat":{"Sail":false}, "Vehicles":null, "Child":null
|
||||
}
|
||||
}`,
|
||||
},
|
||||
}
|
||||
for name, tc := range testcases {
|
||||
tc := tc
|
||||
t.Run(name, func(t *testing.T) {
|
||||
bz, err := json.Marshal(tc.value)
|
||||
require.NoError(t, err)
|
||||
assert.JSONEq(t, tc.output, string(bz))
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user