Skip to content

Commit

Permalink
Return array from json iterator
Browse files Browse the repository at this point in the history
  • Loading branch information
black-adder committed Apr 15, 2019
1 parent 4e8c32c commit 8370f1c
Show file tree
Hide file tree
Showing 3 changed files with 231 additions and 1 deletion.
1 change: 1 addition & 0 deletions document/field/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const (
DoubleType
BytesType
TimeType
ArrayType
)

var (
Expand Down
111 changes: 110 additions & 1 deletion parser/json/value/iterator.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,19 @@ import (
"github.com/xichen2020/eventdb/x/convert"
)

// ArrayAwareIterator ...
type ArrayAwareIterator interface {
field.Iterator

Arr() []*Value
}

// jsonIterator iterates over fields in a JSON value.
type jsonIterator struct {
objs []objectIndex
path []string
value field.ValueUnion
arr []*Value
}

// NewFieldIterator creates a new field iterator from a JSON value.
Expand Down Expand Up @@ -68,8 +76,12 @@ func (it *jsonIterator) Next() bool {
it.path = append(it.path, "")
it.objs = append(it.objs, objectIndex{obj: v.MustObject(), idx: -1})
return it.Next()
case ArrayType:
it.path[lastIdx] = kv.Key()
it.value.Type = field.ArrayType
it.arr = v.MustArray().raw
return true
default:
// NB: Skip arrays.
continue
}
}
Expand All @@ -83,9 +95,106 @@ func (it *jsonIterator) Current() field.Field {
return field.Field{Path: it.path, Value: it.value}
}

func (it *jsonIterator) Arr() []*Value {
ret := it.arr
it.arr = nil
return ret
}

func (it *jsonIterator) Close() {}

type objectIndex struct {
obj Object
idx int
}

type dumper struct {
dumps []Dump
array []*Value
kvArray []KV
idx int
}

// Dump ...
type Dump struct {
Path []string
Value field.ValueUnion
}

// ArrayDump ...
func ArrayDump(path []string, a []*Value) []Dump {
d := &dumper{
dumps: []Dump{},
array: a,
}
d.recurseArr(path)
return d.dumps
}

func (d *dumper) recurseArr(path []string) {
for ; d.idx < len(d.array); d.idx++ {
v := d.array[d.idx]
cpy := make([]string, len(path))
copy(cpy, path)
switch v.Type() {
case NullType:
d.dumps = append(d.dumps, Dump{Path: cpy, Value: field.ValueUnion{Type: field.NullType}})
case BoolType:
d.dumps = append(d.dumps, Dump{Path: cpy, Value: field.ValueUnion{Type: field.BoolType, BoolVal: v.MustBool()}})
case BytesType:
d.dumps = append(d.dumps, Dump{Path: cpy, Value: field.ValueUnion{Type: field.BytesType, BytesVal: bytes.NewImmutableBytes(v.MustBytes())}})
case NumberType:
n := v.MustNumber()
if iv, ok := convert.TryAsInt(n); ok {
d.dumps = append(d.dumps, Dump{Path: cpy, Value: field.ValueUnion{Type: field.IntType, IntVal: iv}})
} else {
d.dumps = append(d.dumps, Dump{Path: cpy, Value: field.ValueUnion{Type: field.DoubleType, DoubleVal: n}})
}
case ObjectType:
nD := &dumper{dumps: []Dump{}, kvArray: v.MustObject().kvs.raw}
nD.recurseObj(cpy)
d.dumps = append(d.dumps, nD.dumps...)
case ArrayType:
nD := &dumper{dumps: []Dump{}, array: v.MustArray().raw}
nD.recurseArr(cpy)
d.dumps = append(d.dumps, nD.dumps...)
default:
continue
}
}
}

func (d *dumper) recurseObj(path []string) {
for ; d.idx < len(d.kvArray); d.idx++ {
kv := d.kvArray[d.idx]
cpy := make([]string, len(path))
copy(cpy, path)
cpy = append(cpy, kv.Key())
v := kv.Value()
switch v.Type() {
case NullType:
d.dumps = append(d.dumps, Dump{Path: cpy, Value: field.ValueUnion{Type: field.NullType}})
case BoolType:
d.dumps = append(d.dumps, Dump{Path: cpy, Value: field.ValueUnion{Type: field.BoolType, BoolVal: v.MustBool()}})
case BytesType:
d.dumps = append(d.dumps, Dump{Path: cpy, Value: field.ValueUnion{Type: field.BytesType, BytesVal: bytes.NewImmutableBytes(v.MustBytes())}})
case NumberType:
n := v.MustNumber()
if iv, ok := convert.TryAsInt(n); ok {
d.dumps = append(d.dumps, Dump{Path: cpy, Value: field.ValueUnion{Type: field.IntType, IntVal: iv}})
} else {
d.dumps = append(d.dumps, Dump{Path: cpy, Value: field.ValueUnion{Type: field.DoubleType, DoubleVal: n}})
}
case ObjectType:
nD := dumper{dumps: []Dump{}, kvArray: v.MustObject().kvs.raw}
nD.recurseObj(cpy)
d.dumps = append(d.dumps, nD.dumps...)
case ArrayType:
nD := dumper{dumps: []Dump{}, array: v.MustArray().raw}
nD.recurseArr(cpy)
d.dumps = append(d.dumps, nD.dumps...)
default:
continue
}
}
}
120 changes: 120 additions & 0 deletions parser/json/value/iterator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ func TestFieldIterator(t *testing.T) {
Path: []string{"xx"},
Value: field.ValueUnion{Type: field.DoubleType, DoubleVal: 33.33},
},
{
Path: []string{"foo"},
Value: field.ValueUnion{Type: field.ArrayType},
},
{
Path: []string{"blah", "bar"},
Value: field.ValueUnion{Type: field.ArrayType},
},
{
Path: []string{"blah", "x"},
Value: field.ValueUnion{Type: field.BytesType, BytesVal: bytes.NewImmutableBytes([]byte("y"))},
Expand Down Expand Up @@ -96,7 +104,119 @@ func compareTestField(t *testing.T, expected, actual field.Field) {
require.Equal(t, expected.Value.DoubleVal, actual.Value.DoubleVal)
case field.BytesType:
require.Equal(t, expected.Value.BytesVal, actual.Value.BytesVal)
case field.ArrayType:
default:
require.Fail(t, "unexpected value type %v", expected.Value.Type)
}
}

func TestArrIterator(t *testing.T) {
// During parsing, how do we know something is an array? we need to call it out in the payload
nestedObj := NewObjectValue(
NewObject(
NewKVArray(
[]KV{
{k: "bar", v: NewArrayValue(NewArray([]*Value{
NewBytesValue([]byte("baz"), nil),
}, nil), nil)},
{k: "x", v: NewBytesValue([]byte("y"), nil)},
{k: "duh", v: NewBoolValue(true, nil)},
{k: "par", v: NewObjectValue(
NewObject(
NewKVArray(
[]KV{
{k: "meh", v: NewNumberValue(3.0, nil)},
{k: "got", v: NewNumberValue(4.5, nil)},
{k: "are", v: NewNullValue(nil)},
}, nil,
),
), nil,
)},
}, nil,
),
), nil,
)

v := NewObjectValue(
NewObject(NewKVArray([]KV{
{k: "xx", v: NewNumberValue(33.33, nil)},
{k: "foo", v: NewArrayValue(
NewArray(
[]*Value{
NewNumberValue(123, nil),
NewBytesValue([]byte("bar"), nil),
nestedObj,
}, nil,
), nil,
)},
}, nil)), nil)

itt := NewFieldIterator(v)
defer itt.Close()
it := itt.(ArrayAwareIterator)

it.Next()
it.Next()
require.Equal(t, field.ArrayType, it.Current().Value.Type)
arr := it.Arr()

expected := []Dump{
{
Path: []string{"foo"},
Value: field.ValueUnion{
Type: field.IntType,
IntVal: 123,
},
},
{
Path: []string{"foo"},
Value: field.ValueUnion{
Type: field.BytesType,
BytesVal: bytes.NewImmutableBytes([]byte("bar")),
},
},
{
Path: []string{"foo", "bar"},
Value: field.ValueUnion{
Type: field.BytesType,
BytesVal: bytes.NewImmutableBytes([]byte("baz")),
},
},
{
Path: []string{"foo", "x"},
Value: field.ValueUnion{
Type: field.BytesType,
BytesVal: bytes.NewImmutableBytes([]byte("y")),
},
},
{
Path: []string{"foo", "duh"},
Value: field.ValueUnion{
Type: field.BoolType,
BoolVal: true,
},
},
{
Path: []string{"foo", "par", "meh"},
Value: field.ValueUnion{
Type: field.IntType,
IntVal: 3,
},
},
{
Path: []string{"foo", "par", "got"},
Value: field.ValueUnion{
Type: field.DoubleType,
DoubleVal: 4.5,
},
},
{
Path: []string{"foo", "par", "are"},
Value: field.ValueUnion{
Type: field.NullType,
},
},
}
dumps := ArrayDump(it.Current().Path, arr)
require.Equal(t, expected, dumps)
}

0 comments on commit 8370f1c

Please sign in to comment.