diff --git a/feature_any.go b/feature_any.go index 189aa55..87b0e29 100644 --- a/feature_any.go +++ b/feature_any.go @@ -1,7 +1,5 @@ package jsoniter -import "fmt" - type Any interface { LastError() error ToBool() bool @@ -12,12 +10,17 @@ type Any interface { ToFloat64() float64 ToString() string Get(path ...interface{}) Any + Size() int Keys() []string IterateObject() (func() (string, Any, bool), bool) } type baseAny struct {} +func (any *baseAny) Size() int { + return 0 +} + func (any *baseAny) Keys() []string { return []string{} } @@ -38,9 +41,6 @@ func (iter *Iterator) readAny(reusableIter *Iterator) Any { case 'n': iter.skipFixedBytes(3) // null return &nilAny{} - case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - iter.unreadByte() - return iter.readNumberAny(reusableIter) case 't': iter.skipFixedBytes(3) // true return &trueAny{} @@ -49,9 +49,12 @@ func (iter *Iterator) readAny(reusableIter *Iterator) Any { return &falseAny{} case '{': return iter.readObjectAny(reusableIter) + case '[': + return iter.readArrayAny(reusableIter) + default: + iter.unreadByte() + return iter.readNumberAny(reusableIter) } - iter.reportError("ReadAny", fmt.Sprintf("unexpected character: %v", c)) - return &invalidAny{} } func (iter *Iterator) readNumberAny(reusableIter *Iterator) Any { @@ -130,7 +133,7 @@ func (iter *Iterator) readObjectAny(reusableIter *Iterator) Any { if level == 0 { iter.head = i + 1 lazyBuf = append(lazyBuf, iter.buf[start:iter.head]...) - return &objectLazyAny{lazyBuf, reusableIter, nil, nil, lazyBuf} + return &objectLazyAny{baseAny{}, lazyBuf, reusableIter, nil, nil, lazyBuf} } } } @@ -141,3 +144,36 @@ func (iter *Iterator) readObjectAny(reusableIter *Iterator) Any { } } } + +func (iter *Iterator) readArrayAny(reusableIter *Iterator) Any { + level := 1 + lazyBuf := make([]byte, 1, 32) + lazyBuf[0] = '{' + for { + start := iter.head + for i := iter.head; i < iter.tail; i++ { + switch iter.buf[i] { + case '"': // If inside string, skip it + iter.head = i + 1 + iter.skipString() + i = iter.head - 1 // it will be i++ soon + case '[': // If open symbol, increase level + level++ + case ']': // If close symbol, increase level + level-- + + // If we have returned to the original level, we're done + if level == 0 { + iter.head = i + 1 + lazyBuf = append(lazyBuf, iter.buf[start:iter.head]...) + return &arrayLazyAny{baseAny{},lazyBuf, reusableIter, nil, nil, lazyBuf} + } + } + } + lazyBuf = append(lazyBuf, iter.buf[iter.head:iter.tail]...) + if !iter.loadMore() { + iter.reportError("skipArray", "incomplete array") + return &invalidAny{} + } + } +} diff --git a/feature_any_array.go b/feature_any_array.go new file mode 100644 index 0000000..94912e6 --- /dev/null +++ b/feature_any_array.go @@ -0,0 +1,127 @@ +package jsoniter + +type arrayLazyAny struct { + baseAny + buf []byte + iter *Iterator + err error + cache []Any + remaining []byte +} + +func (any *arrayLazyAny) parse() *Iterator { + iter := any.iter + if iter == nil { + iter = NewIterator() + any.iter = iter + } + iter.ResetBytes(any.remaining) + return iter +} + +func (any *arrayLazyAny) fillCacheUntil(target int) Any { + if any.remaining == nil { + if target >= len(any.cache) { + return nil + } + return any.cache[target] + } + i := len(any.cache) + if target < i { + return any.cache[target] + } + iter := any.parse() + if (len(any.remaining) == len(any.buf)) { + iter.head++ + c := iter.nextToken() + if c != ']' { + iter.unreadByte() + element := iter.readAny(iter) + any.cache = append(any.cache, element) + if target == 0 { + any.remaining = iter.buf[iter.head:] + return element + } + i = 1 + } else { + any.remaining = nil + return nil + } + } + for iter.nextToken() == ',' { + element := iter.readAny(iter) + any.cache = append(any.cache, element) + if i == target { + any.remaining = iter.buf[iter.head:] + return element + } + i++ + } + any.remaining = nil + return nil +} + +func (any *arrayLazyAny) fillCache() { + if any.remaining == nil { + return + } + iter := any.parse() + if len(any.remaining) == len(any.buf) { + iter.head++ + c := iter.nextToken() + if c != ']' { + iter.unreadByte() + any.cache = append(any.cache, iter.readAny(iter)) + } else { + any.remaining = nil + return + } + } + for iter.nextToken() == ',' { + any.cache = append(any.cache, iter.readAny(iter)) + } + any.remaining = nil + return +} + +func (any *arrayLazyAny) LastError() error { + return nil +} + +func (any *arrayLazyAny) ToBool() bool { + return false +} + +func (any *arrayLazyAny) ToInt() int { + return 0 +} + +func (any *arrayLazyAny) ToInt32() int32 { + return 0 +} + +func (any *arrayLazyAny) ToInt64() int64 { + return 0 +} + +func (any *arrayLazyAny) ToFloat32() float32 { + return 0 +} + +func (any *arrayLazyAny) ToFloat64() float64 { + return 0 +} + +func (any *arrayLazyAny) ToString() string { + return "" +} + +func (any *arrayLazyAny) Get(path ...interface{}) Any { + idx := path[0].(int) + return any.fillCacheUntil(idx) +} + +func (any *arrayLazyAny) Size() int { + any.fillCache() + return len(any.cache) +} diff --git a/feature_any_object.go b/feature_any_object.go index 70fc998..6fc929d 100644 --- a/feature_any_object.go +++ b/feature_any_object.go @@ -5,6 +5,7 @@ import ( ) type objectLazyAny struct { + baseAny buf []byte iter *Iterator err error @@ -189,9 +190,6 @@ func (any *objectLazyAny) IterateObject() (func() (string, Any, bool), bool) { i++ return key, value, true } else { - if remaining == nil { - return "", nil, false - } // read from buffer iter := any.iter if iter == nil { diff --git a/feature_iter.go b/feature_iter.go index 12a34cd..5f2b0a0 100644 --- a/feature_iter.go +++ b/feature_iter.go @@ -198,14 +198,18 @@ func (iter *Iterator) readByte() (ret byte) { func (iter *Iterator) loadMore() bool { if iter.reader == nil { - iter.Error = io.EOF + if iter.Error == nil { + iter.Error = io.EOF + } return false } for { n, err := iter.reader.Read(iter.buf) if n == 0 { if err != nil { - iter.Error = err + if iter.Error == nil { + iter.Error = err + } return false } } else { diff --git a/jsoniter_array_test.go b/jsoniter_array_test.go index 7dd5963..c7bcab7 100644 --- a/jsoniter_array_test.go +++ b/jsoniter_array_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/json-iterator/go/require" "bytes" + "io" ) func Test_empty_array(t *testing.T) { @@ -44,10 +45,31 @@ func Test_two_elements(t *testing.T) { should.Equal([]interface{}{float64(1), float64(2)}, iter.Read()) } +func Test_read_empty_array_as_any(t *testing.T) { + should := require.New(t) + any, err := UnmarshalAnyFromString("[]") + should.Nil(err) + should.Equal(0, any.Size()) +} + +func Test_read_one_element_array_as_any(t *testing.T) { + should := require.New(t) + any, err := UnmarshalAnyFromString("[1]") + should.Nil(err) + should.Equal(1, any.Size()) +} + +func Test_read_two_element_array_as_any(t *testing.T) { + should := require.New(t) + any, err := UnmarshalAnyFromString("[1,2]") + should.Nil(err) + should.Equal(1, any.Get(0).ToInt()) + should.Equal(2, any.Size()) +} + func Test_invalid_array(t *testing.T) { - iter := ParseString(`[`) - iter.ReadArray() - if iter.Error == nil { + _, err := UnmarshalAnyFromString("[") + if err == nil || err == io.EOF { t.FailNow() } }