From b9fe012eeaef57a5a384e6a7d571a7aae635c335 Mon Sep 17 00:00:00 2001 From: Tao Wen Date: Mon, 23 Jan 2017 08:33:43 +0800 Subject: [PATCH] string any --- feature_adapter.go | 19 +++++--- feature_any.go | 50 ++++++++++++++++----- feature_any_invalid.go | 36 +++++++++++++++ feature_any_string.go | 98 +++++++++++++++++++++++++++++++++++++++++ feature_iter.go | 4 +- feature_iter_skip.go | 1 + jsoniter_string_test.go | 16 ++++++- 7 files changed, 206 insertions(+), 18 deletions(-) create mode 100644 feature_any_invalid.go create mode 100644 feature_any_string.go diff --git a/feature_adapter.go b/feature_adapter.go index 5ba16f4..c799127 100644 --- a/feature_adapter.go +++ b/feature_adapter.go @@ -2,7 +2,6 @@ package jsoniter import ( "io" - "unsafe" "bytes" ) @@ -10,6 +9,9 @@ import ( func Unmarshal(data []byte, v interface{}) error { iter := ParseBytes(data) iter.ReadVal(v) + if iter.head == iter.tail { + iter.loadMore() + } if iter.Error == io.EOF { return nil } @@ -22,6 +24,9 @@ func Unmarshal(data []byte, v interface{}) error { func UnmarshalAny(data []byte) (Any, error) { iter := ParseBytes(data) any := iter.ReadAny() + if iter.head == iter.tail { + iter.loadMore() + } if iter.Error == io.EOF { return any, nil } @@ -32,10 +37,12 @@ func UnmarshalAny(data []byte) (Any, 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)) + data := []byte(str) iter := ParseBytes(data) iter.ReadVal(v) + if iter.head == iter.tail { + iter.loadMore() + } if iter.Error == io.EOF { return nil } @@ -46,10 +53,12 @@ func UnmarshalFromString(str string, v interface{}) 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)) + data := []byte(str) iter := ParseBytes(data) any := iter.ReadAny() + if iter.head == iter.tail { + iter.loadMore() + } if iter.Error == io.EOF { return any, nil } diff --git a/feature_any.go b/feature_any.go index 491785c..f393472 100644 --- a/feature_any.go +++ b/feature_any.go @@ -20,18 +20,15 @@ func (iter *Iterator) ReadAny() Any { iter.skipFixedBytes(4) return &nilAny{} case Number: - dotFound, lazyBuf := iter.skipNumber() - if dotFound { - return &floatLazyAny{lazyBuf, nil, nil, 0} - } else { - return &intLazyAny{lazyBuf, nil, nil, 0} - } + return iter.readNumberAny() + case String: + return iter.readStringAny() } iter.reportError("ReadAny", fmt.Sprintf("unexpected value type: %v", valueType)) - return nil + return &invalidAny{} } -func (iter *Iterator) skipNumber() (bool, []byte) { +func (iter *Iterator) readNumberAny() Any { dotFound := false var lazyBuf []byte for { @@ -45,13 +42,44 @@ func (iter *Iterator) skipNumber() (bool, []byte) { case ' ', '\n', '\r', '\t', ',', '}', ']': lazyBuf = append(lazyBuf, iter.buf[iter.head:i]...) iter.head = i - return dotFound, lazyBuf + if dotFound { + return &floatLazyAny{lazyBuf, nil, nil, 0} + } else { + return &intLazyAny{lazyBuf, nil, nil, 0} + } } } lazyBuf = append(lazyBuf, iter.buf[iter.head:iter.tail]...) if !iter.loadMore() { - iter.head = iter.tail; - return dotFound, lazyBuf + iter.head = iter.tail + if dotFound { + return &floatLazyAny{lazyBuf, nil, nil, 0} + } else { + return &intLazyAny{lazyBuf, nil, nil, 0} + } + } + } +} + +func (iter *Iterator) readStringAny() Any { + iter.head++ + lazyBuf := make([]byte, 1, 8) + lazyBuf[0] = '"' + for { + end, escaped := iter.findStringEnd() + if end == -1 { + lazyBuf = append(lazyBuf, iter.buf[iter.head:iter.tail]...) + if !iter.loadMore() { + iter.reportError("readStringAny", "incomplete string") + return &invalidAny{} + } + if escaped { + iter.head = 1 // skip the first char as last char read is \ + } + } else { + lazyBuf = append(lazyBuf, iter.buf[iter.head:end]...) + iter.head = end + return &stringLazyAny{lazyBuf, nil, nil, ""} } } } diff --git a/feature_any_invalid.go b/feature_any_invalid.go new file mode 100644 index 0000000..2e05d7d --- /dev/null +++ b/feature_any_invalid.go @@ -0,0 +1,36 @@ +package jsoniter + +type invalidAny struct { +} + +func (any *invalidAny) LastError() error { + return nil +} + +func (any *invalidAny) ToBool() bool { + return false +} + +func (any *invalidAny) ToInt() int { + return 0 +} + +func (any *invalidAny) ToInt32() int32 { + return 0 +} + +func (any *invalidAny) ToInt64() int64 { + return 0 +} + +func (any *invalidAny) ToFloat32() float32 { + return 0 +} + +func (any *invalidAny) ToFloat64() float64 { + return 0 +} + +func (any *invalidAny) ToString() string { + return "" +} diff --git a/feature_any_string.go b/feature_any_string.go new file mode 100644 index 0000000..45ae405 --- /dev/null +++ b/feature_any_string.go @@ -0,0 +1,98 @@ +package jsoniter + +import ( + "io" +) + +type stringLazyAny struct{ + buf []byte + iter *Iterator + err error + cache string +} + +func (any *stringLazyAny) fillCache() { + if any.err != nil { + return + } + iter := any.parse() + any.cache = iter.ReadString() + if iter.Error != io.EOF { + iter.reportError("stringLazyAny", "there are bytes left") + } + any.err = iter.Error +} + +func (any *stringLazyAny) parse() *Iterator { + iter := any.iter + if iter == nil { + iter = NewIterator() + any.iter = iter + } + iter.ResetBytes(any.buf) + return iter +} + +func (any *stringLazyAny) LastError() error { + return any.err +} + +func (any *stringLazyAny) ToBool() bool { + str := any.ToString() + if str == "false" { + return false + } + for _, c := range str { + switch c { + case ' ', '\n', '\r', '\t': + default: + return true + } + } + return false +} + +func (any *stringLazyAny) ToInt() int { + iter := any.parse() + iter.head++ + val := iter.ReadInt() + any.err = iter.Error + return val +} + +func (any *stringLazyAny) ToInt32() int32 { + iter := any.parse() + iter.head++ + val := iter.ReadInt32() + any.err = iter.Error + return val +} + +func (any *stringLazyAny) ToInt64() int64 { + iter := any.parse() + iter.head++ + val := iter.ReadInt64() + any.err = iter.Error + return val +} + +func (any *stringLazyAny) ToFloat32() float32 { + iter := any.parse() + iter.head++ + val := iter.ReadFloat32() + any.err = iter.Error + return val +} + +func (any *stringLazyAny) ToFloat64() float64 { + iter := any.parse() + iter.head++ + val := iter.ReadFloat64() + any.err = iter.Error + return val +} + +func (any *stringLazyAny) ToString() string { + any.fillCache() + return any.cache +} \ No newline at end of file diff --git a/feature_iter.go b/feature_iter.go index 078d1af..12a34cd 100644 --- a/feature_iter.go +++ b/feature_iter.go @@ -160,7 +160,9 @@ func (iter *Iterator) nextToken() byte { func (iter *Iterator) reportError(operation string, msg string) { if iter.Error != nil { - return + if iter.Error != io.EOF { + return + } } peekStart := iter.head - 10 if peekStart < 0 { diff --git a/feature_iter_skip.go b/feature_iter_skip.go index 2d9c1fd..582098f 100644 --- a/feature_iter_skip.go +++ b/feature_iter_skip.go @@ -57,6 +57,7 @@ func (iter *Iterator) skipString() { end, escaped := iter.findStringEnd() if end == -1 { if !iter.loadMore() { + iter.reportError("skipString", "incomplete string") return } if escaped { diff --git a/jsoniter_string_test.go b/jsoniter_string_test.go index 33fbd85..f134afa 100644 --- a/jsoniter_string_test.go +++ b/jsoniter_string_test.go @@ -59,12 +59,26 @@ func Test_read_exotic_string(t *testing.T) { } } -func Test_read_string_via_read(t *testing.T) { +func Test_read_string_as_interface(t *testing.T) { should := require.New(t) iter := ParseString(`"hello"`) should.Equal("hello", iter.Read()) } +func Test_read_string_as_any(t *testing.T) { + should := require.New(t) + any, err := UnmarshalAnyFromString(`"hello"`) + should.Nil(err) + should.Equal("hello", any.ToString()) + should.True(any.ToBool()) + any, err = UnmarshalAnyFromString(`" "`) + should.False(any.ToBool()) + any, err = UnmarshalAnyFromString(`"false"`) + should.False(any.ToBool()) + any, err = UnmarshalAnyFromString(`"123"`) + should.Equal(123, any.ToInt()) +} + func Test_write_string(t *testing.T) { should := require.New(t) str, err := MarshalToString("hello")