diff --git a/feature_adapter.go b/feature_adapter.go index 0fd10dc..5ba16f4 100644 --- a/feature_adapter.go +++ b/feature_adapter.go @@ -13,9 +13,24 @@ func Unmarshal(data []byte, v interface{}) error { if iter.Error == io.EOF { return nil } + if iter.Error == nil { + iter.reportError("UnmarshalAny", "there are bytes left after unmarshal") + } return iter.Error } +func UnmarshalAny(data []byte) (Any, error) { + iter := ParseBytes(data) + any := iter.ReadAny() + if iter.Error == io.EOF { + return any, nil + } + if iter.Error == nil { + iter.reportError("UnmarshalAny", "there are bytes left after unmarshal") + } + return any, iter.Error +} + func UnmarshalFromString(str string, v interface{}) error { // safe to do the unsafe cast here, as str is always referenced in this scope data := *(*[]byte)(unsafe.Pointer(&str)) @@ -24,9 +39,26 @@ func UnmarshalFromString(str string, v interface{}) error { if iter.Error == io.EOF { return nil } + if iter.Error == nil { + iter.reportError("UnmarshalFromString", "there are bytes left after unmarshal") + } return iter.Error } +func UnmarshalAnyFromString(str string) (Any, error) { + // safe to do the unsafe cast here, as str is always referenced in this scope + data := *(*[]byte)(unsafe.Pointer(&str)) + iter := ParseBytes(data) + any := iter.ReadAny() + if iter.Error == io.EOF { + return any, nil + } + if iter.Error == nil { + iter.reportError("UnmarshalAnyFromString", "there are bytes left after unmarshal") + } + return nil, iter.Error +} + func Marshal(v interface{}) ([]byte, error) { buf := &bytes.Buffer{} stream := NewStream(buf, 4096) diff --git a/feature_any.go b/feature_any.go new file mode 100644 index 0000000..f7aa681 --- /dev/null +++ b/feature_any.go @@ -0,0 +1,57 @@ +package jsoniter + +import "fmt" + +type Any interface { + LastError() error + ToBool() bool + ToInt() int + ToInt32() int32 + ToInt64() int64 + ToFloat32() float32 + ToFloat64() float64 + ToString() string +} + +func (iter *Iterator) ReadAny() Any { + valueType := iter.WhatIsNext() + switch valueType { + case Nil: + iter.skipFixedBytes(4) + return &nilAny{} + case Number: + dotFound, lazyBuf := iter.skipNumber() + if dotFound { + return &floatLazyAny{lazyBuf, nil, nil} + } else { + return &intLazyAny{lazyBuf, nil, nil, 0} + } + } + iter.reportError("ReadAny", fmt.Sprintf("unexpected value type: %v", valueType)) + return nil +} + +func (iter *Iterator) skipNumber() (bool, []byte) { + dotFound := false + var lazyBuf []byte + for { + for i := iter.head; i < iter.tail; i++ { + c := iter.buf[i] + if c == '.' { + dotFound = true + continue + } + switch c { + case ' ', '\n', '\r', '\t', ',', '}', ']': + lazyBuf = append(lazyBuf, iter.buf[iter.head:i]...) + iter.head = i + return dotFound, lazyBuf + } + } + lazyBuf = append(lazyBuf, iter.buf[iter.head:iter.tail]...) + if !iter.loadMore() { + iter.head = iter.tail; + return dotFound, lazyBuf + } + } +} diff --git a/feature_any_float.go b/feature_any_float.go new file mode 100644 index 0000000..9578903 --- /dev/null +++ b/feature_any_float.go @@ -0,0 +1,39 @@ +package jsoniter + +type floatLazyAny struct { + buf []byte + iter *Iterator + err error +} + +func (any *floatLazyAny) LastError() error { + return any.err +} + +func (any *floatLazyAny) ToBool() bool { + return false +} + +func (any *floatLazyAny) ToInt() int { + return 0 +} + +func (any *floatLazyAny) ToInt32() int32 { + return 0 +} + +func (any *floatLazyAny) ToInt64() int64 { + return 0 +} + +func (any *floatLazyAny) ToFloat32() float32 { + return 0 +} + +func (any *floatLazyAny) ToFloat64() float64 { + return 0 +} + +func (any *floatLazyAny) ToString() string { + return "" +} \ No newline at end of file diff --git a/feature_any_int.go b/feature_any_int.go new file mode 100644 index 0000000..b2708f4 --- /dev/null +++ b/feature_any_int.go @@ -0,0 +1,66 @@ +package jsoniter + +import ( + "io" + "unsafe" +) + +type intLazyAny struct { + buf []byte + iter *Iterator + err error + cache int64 +} + +func (any *intLazyAny) fillCache() { + if any.err != nil { + return + } + iter := any.iter + if iter == nil { + iter = NewIterator() + } + iter.ResetBytes(any.buf) + any.cache = iter.ReadInt64() + if iter.Error != io.EOF { + iter.reportError("intLazyAny", "there are bytes left") + } + any.err = iter.Error +} + +func (any *intLazyAny) LastError() error { + return any.err +} + +func (any *intLazyAny) ToBool() bool { + return any.ToInt64() != 0 +} + +func (any *intLazyAny) ToInt() int { + any.fillCache() + return int(any.cache) +} + +func (any *intLazyAny) ToInt32() int32 { + any.fillCache() + return int32(any.cache) +} + +func (any *intLazyAny) ToInt64() int64 { + any.fillCache() + return any.cache +} + +func (any *intLazyAny) ToFloat32() float32 { + any.fillCache() + return float32(any.cache) +} + +func (any *intLazyAny) ToFloat64() float64 { + any.fillCache() + return float64(any.cache) +} + +func (any *intLazyAny) ToString() string { + return *(*string)(unsafe.Pointer(&any.buf)) +} \ No newline at end of file diff --git a/feature_any_nil.go b/feature_any_nil.go new file mode 100644 index 0000000..cd3067a --- /dev/null +++ b/feature_any_nil.go @@ -0,0 +1,36 @@ +package jsoniter + +type nilAny struct { +} + +func (any *nilAny) LastError() error { + return nil +} + +func (any *nilAny) ToBool() bool { + return false +} + +func (any *nilAny) ToInt() int { + return 0 +} + +func (any *nilAny) ToInt32() int32 { + return 0 +} + +func (any *nilAny) ToInt64() int64 { + return 0 +} + +func (any *nilAny) ToFloat32() float32 { + return 0 +} + +func (any *nilAny) ToFloat64() float64 { + return 0 +} + +func (any *nilAny) ToString() string { + return "" +} diff --git a/jsoniter_int_test.go b/jsoniter_int_test.go index b1114e5..3f30707 100644 --- a/jsoniter_int_test.go +++ b/jsoniter_int_test.go @@ -8,6 +8,7 @@ import ( "fmt" "strconv" "io/ioutil" + "io" ) func Test_read_uint64_invalid(t *testing.T) { @@ -99,6 +100,16 @@ func Test_read_int64_overflow(t *testing.T) { should.NotNil(iter.Error) } +func Test_read_int64_as_any(t *testing.T) { + should := require.New(t) + any, err := UnmarshalAnyFromString("1234") + should.Nil(err) + should.Equal(1234, any.ToInt()) + should.Equal(io.EOF, any.LastError()) + should.Equal("1234", any.ToString()) + should.True(any.ToBool()) +} + func Test_write_uint8(t *testing.T) { vals := []uint8{0, 1, 11, 111, 255} for _, val := range vals { diff --git a/jsoniter_null_test.go b/jsoniter_null_test.go index f4de215..c0b576f 100644 --- a/jsoniter_null_test.go +++ b/jsoniter_null_test.go @@ -12,6 +12,13 @@ func Test_read_null(t *testing.T) { should.True(iter.ReadNil()) iter = ParseString(`null`) should.Nil(iter.Read()) + iter = ParseString(`null`) + any, err := UnmarshalAnyFromString(`null`) + should.Nil(err) + should.Equal(0, any.ToInt()) + should.Equal(float64(0), any.ToFloat64()) + should.Equal("", any.ToString()) + should.False(any.ToBool()) } func Test_write_null(t *testing.T) {