forked from TrueCloudLab/neoneo-go
stackitem: implement unmarshaling from JSON with types
Signed-off-by: Evgenii Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
parent
ee5ff40b17
commit
9d6ce4bdcc
2 changed files with 160 additions and 0 deletions
|
@ -264,3 +264,107 @@ func toJSONWithTypes(item Item, seen map[Item]bool) (interface{}, error) {
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
rawItem struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Value json.RawMessage `json:"value,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
rawMapElement struct {
|
||||||
|
Key json.RawMessage `json:"key"`
|
||||||
|
Value json.RawMessage `json:"value"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// FromJSONWithTypes deserializes an item from typed-json representation.
|
||||||
|
func FromJSONWithTypes(data []byte) (Item, error) {
|
||||||
|
raw := new(rawItem)
|
||||||
|
if err := json.Unmarshal(data, raw); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
typ, err := FromString(raw.Type)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("invalid type")
|
||||||
|
}
|
||||||
|
switch typ {
|
||||||
|
case AnyT:
|
||||||
|
return Null{}, nil
|
||||||
|
case PointerT:
|
||||||
|
var pos int
|
||||||
|
if err := json.Unmarshal(raw.Value, &pos); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewPointer(pos, nil), nil
|
||||||
|
case BooleanT:
|
||||||
|
var b bool
|
||||||
|
if err := json.Unmarshal(raw.Value, &b); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewBool(b), nil
|
||||||
|
case IntegerT:
|
||||||
|
var s string
|
||||||
|
if err := json.Unmarshal(raw.Value, &s); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
val, ok := new(big.Int).SetString(s, 10)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("invalid integer")
|
||||||
|
}
|
||||||
|
return NewBigInteger(val), nil
|
||||||
|
case ByteArrayT, BufferT:
|
||||||
|
var s string
|
||||||
|
if err := json.Unmarshal(raw.Value, &s); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
val, err := base64.StdEncoding.DecodeString(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if typ == ByteArrayT {
|
||||||
|
return NewByteArray(val), nil
|
||||||
|
}
|
||||||
|
return NewBuffer(val), nil
|
||||||
|
case ArrayT, StructT:
|
||||||
|
var arr []json.RawMessage
|
||||||
|
if err := json.Unmarshal(raw.Value, &arr); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items := make([]Item, len(arr))
|
||||||
|
for i := range arr {
|
||||||
|
it, err := FromJSONWithTypes(arr[i])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items[i] = it
|
||||||
|
}
|
||||||
|
if typ == ArrayT {
|
||||||
|
return NewArray(items), nil
|
||||||
|
}
|
||||||
|
return NewStruct(items), nil
|
||||||
|
case MapT:
|
||||||
|
var arr []rawMapElement
|
||||||
|
if err := json.Unmarshal(raw.Value, &arr); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m := NewMap()
|
||||||
|
for i := range arr {
|
||||||
|
key, err := FromJSONWithTypes(arr[i].Key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if !IsValidMapKey(key) {
|
||||||
|
return nil, fmt.Errorf("invalid map key of type %s", key.Type())
|
||||||
|
}
|
||||||
|
value, err := FromJSONWithTypes(arr[i].Value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m.Add(key, value)
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
case InteropT:
|
||||||
|
return NewInterop(nil), nil
|
||||||
|
default:
|
||||||
|
return nil, errors.New("unexpected type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -155,12 +155,19 @@ func TestToJSONWithTypes(t *testing.T) {
|
||||||
{"BoolFalse", NewBool(false), `{"type":"Boolean","value":false}`},
|
{"BoolFalse", NewBool(false), `{"type":"Boolean","value":false}`},
|
||||||
{"Struct", NewStruct([]Item{Make(11)}),
|
{"Struct", NewStruct([]Item{Make(11)}),
|
||||||
`{"type":"Struct","value":[{"type":"Integer","value":"11"}]}`},
|
`{"type":"Struct","value":[{"type":"Integer","value":"11"}]}`},
|
||||||
|
{"Map", NewMapWithValue([]MapElement{{Key: NewBigInteger(big.NewInt(42)), Value: NewBool(false)}}),
|
||||||
|
`{"type":"Map","value":[{"key":{"type":"Integer","value":"42"},` +
|
||||||
|
`"value":{"type":"Boolean","value":false}}]}`},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
s, err := ToJSONWithTypes(tc.item)
|
s, err := ToJSONWithTypes(tc.item)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, tc.result, string(s))
|
require.Equal(t, tc.result, string(s))
|
||||||
|
|
||||||
|
item, err := FromJSONWithTypes(s)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, tc.item, item)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,3 +189,52 @@ func TestToJSONWithTypes(t *testing.T) {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFromJSONWithTypes(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
json string
|
||||||
|
item Item
|
||||||
|
}{
|
||||||
|
{"Pointer", `{"type":"Pointer","value":3}`, NewPointer(3, nil)},
|
||||||
|
{"Interop", `{"type":"Interop"}`, NewInterop(nil)},
|
||||||
|
{"Null", `{"type":"Any"}`, Null{}},
|
||||||
|
{"Array", `{"type":"Array","value":[{"type":"Any"}]}`, NewArray([]Item{Null{}})},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
item, err := FromJSONWithTypes([]byte(tc.json))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, tc.item, item)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("Invalid", func(t *testing.T) {
|
||||||
|
errCases := []struct {
|
||||||
|
name string
|
||||||
|
json string
|
||||||
|
}{
|
||||||
|
{"InvalidType", `{"type":int,"value":"4"`},
|
||||||
|
{"UnexpectedType", `{"type":"int","value":"4"}`},
|
||||||
|
{"IntegerValue1", `{"type":"Integer","value": 4}`},
|
||||||
|
{"IntegerValue2", `{"type":"Integer","value": "a"}`},
|
||||||
|
{"BoolValue", `{"type":"Boolean","value": "str"}`},
|
||||||
|
{"PointerValue", `{"type":"Pointer","value": "str"}`},
|
||||||
|
{"BufferValue1", `{"type":"Buffer","value":"not a base 64"}`},
|
||||||
|
{"BufferValue2", `{"type":"Buffer","value":123}`},
|
||||||
|
{"ArrayValue", `{"type":"Array","value":3}`},
|
||||||
|
{"ArrayElement", `{"type":"Array","value":[3]}`},
|
||||||
|
{"MapValue", `{"type":"Map","value":3}`},
|
||||||
|
{"MapElement", `{"type":"Map","value":[{"key":"value"}]}`},
|
||||||
|
{"MapElementKeyNotPrimitive", `{"type":"Map","value":[{"key":{"type":"Any"}}]}`},
|
||||||
|
{"MapElementValue", `{"type":"Map","value":[` +
|
||||||
|
`{"key":{"type":"Integer","value":"3"},"value":3}]}`},
|
||||||
|
}
|
||||||
|
for _, tc := range errCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
_, err := FromJSONWithTypes([]byte(tc.json))
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue