From 0e2642f91048cb1415fd5a5341e722c3304ec2d4 Mon Sep 17 00:00:00 2001 From: Skip Baney Date: Mon, 12 Aug 2024 22:52:56 -0600 Subject: [PATCH 1/3] Add remove method to Indexed interface --- jp/indexed.go | 3 +++ jp/ordered_test.go | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/jp/indexed.go b/jp/indexed.go index 6bb4843..bbe3c63 100644 --- a/jp/indexed.go +++ b/jp/indexed.go @@ -13,6 +13,9 @@ type Indexed interface { // SetValueAtIndex should set the value at the provided index. SetValueAtIndex(index int, value any) + // RemoveValueAtIndex removes an item from the collection. + RemoveValueAtIndex(index int) + // Size should return the size for the collection. Size() int } diff --git a/jp/ordered_test.go b/jp/ordered_test.go index 6091b31..3f3fc92 100644 --- a/jp/ordered_test.go +++ b/jp/ordered_test.go @@ -64,6 +64,13 @@ func (o *ordered) SetValueAtIndex(index int, value any) { } } +func (o *ordered) RemoveValueAtIndex(index int) { + if 0 <= index && index < len(o.entries) { + copy(o.entries[index:], o.entries[index+1:]) + o.entries = o.entries[:len(o.entries)-1] + } +} + type keyed struct { ordered } From 9ca46125cc9cfeff971dfbf0a942907fb55533fd Mon Sep 17 00:00:00 2001 From: Skip Baney Date: Mon, 12 Aug 2024 22:57:27 -0600 Subject: [PATCH 2/3] Support Indexed and Keyed interfaces when removing nodes --- jp/child.go | 2 + jp/filter.go | 39 ++++++++++++ jp/nth.go | 9 +++ jp/remove_test.go | 158 ++++++++++++++++++++++++++++++++++++++++++++++ jp/slice.go | 41 ++++++++++++ jp/union.go | 35 ++++++++++ jp/wildcard.go | 26 ++++++++ 7 files changed, 310 insertions(+) diff --git a/jp/child.go b/jp/child.go index e35a70d..bab381b 100644 --- a/jp/child.go +++ b/jp/child.go @@ -48,6 +48,8 @@ func (f Child) remove(value any) (out any, changed bool) { if _, changed = tv[key]; changed { delete(tv, key) } + case Keyed: + tv.RemoveValueForKey(key) default: if rt := reflect.TypeOf(value); rt != nil { // Can't remove a field from a struct so only a map can be modified. diff --git a/jp/filter.go b/jp/filter.go index f8ed25e..28b6415 100644 --- a/jp/filter.go +++ b/jp/filter.go @@ -98,6 +98,24 @@ func (f Filter) remove(value any) (out any, changed bool) { changed = true } } + case Indexed: + size := tv.Size() + for i := (size - 1); i >= 0; i-- { + v := tv.ValueAtIndex(i) + if f.Match(v) { + tv.RemoveValueAtIndex(i) + changed = true + } + } + case Keyed: + keys := tv.Keys() + for _, key := range keys { + v, _ := tv.ValueForKey(key) + if f.Match(v) { + tv.RemoveValueForKey(key) + changed = true + } + } default: rv := reflect.ValueOf(value) switch rv.Kind() { @@ -201,6 +219,27 @@ func (f Filter) removeOne(value any) (out any, changed bool) { } } } + case Indexed: + size := tv.Size() + for i := 0; i < size; i++ { + v := tv.ValueAtIndex(i) + if f.Match(v) { + tv.RemoveValueAtIndex(i) + changed = true + break + } + } + case Keyed: + keys := tv.Keys() + sort.Strings(keys) + for _, key := range keys { + v, _ := tv.ValueForKey(key) + if f.Match(v) { + tv.RemoveValueForKey(key) + changed = true + break + } + } default: rv := reflect.ValueOf(value) switch rv.Kind() { diff --git a/jp/nth.go b/jp/nth.go index 8310735..e2ef04b 100644 --- a/jp/nth.go +++ b/jp/nth.go @@ -59,6 +59,15 @@ func (f Nth) remove(value any) (out any, changed bool) { out = append(tv[:i], tv[i+1:]...) changed = true } + case Indexed: + size := tv.Size() + if i < 0 { + i = size + i + } + if 0 <= i && i < size { + tv.RemoveValueAtIndex(i) + changed = true + } default: if rt := reflect.TypeOf(value); rt != nil { if rt.Kind() == reflect.Slice { diff --git a/jp/remove_test.go b/jp/remove_test.go index 95f4b8e..e4a5ee3 100644 --- a/jp/remove_test.go +++ b/jp/remove_test.go @@ -666,3 +666,161 @@ func TestExprRemoveFilterMap(t *testing.T) { result = x.MustRemoveOne(data) tt.Equal(t, "{b: {x: 2} c: {x: 3}}", string(pw.Encode(result))) } + +func TestExprRemoveIndexedFilter(t *testing.T) { + x := jp.MustParseString("$[2][?@ > 11]") + + data := indexedData() + result := x.MustRemove(data) + tt.Equal(t, 11, jp.N(2).N(0).First(result)) + tt.Equal(t, false, jp.N(2).N(1).Has(result)) + tt.Equal(t, false, jp.N(2).N(2).Has(result)) + + data = indexedData() + result = x.MustRemoveOne(data) + tt.Equal(t, 11, jp.N(2).N(0).First(result)) + tt.Equal(t, 13, jp.N(2).N(1).First(result)) + tt.Equal(t, false, jp.N(2).N(2).Has(result)) +} + +func TestExprRemoveIndexedNth(t *testing.T) { + x := jp.MustParseString("$[2][1]") + data := indexedData() + result := x.MustRemove(data) + tt.Equal(t, 11, jp.N(2).N(0).First(result)) + tt.Equal(t, 13, jp.N(2).N(1).First(result)) + tt.Equal(t, false, jp.N(2).N(2).Has(result)) + + x = jp.MustParseString("$[2][-1]") + data = indexedData() + result = x.MustRemove(data) + tt.Equal(t, 11, jp.N(2).N(0).First(result)) + tt.Equal(t, 12, jp.N(2).N(1).First(result)) + tt.Equal(t, false, jp.N(2).N(2).Has(result)) +} + +func TestExprRemoveIndexedSlice(t *testing.T) { + x := jp.MustParseString("$[2][0:1]") // first two + data := indexedData() + result := x.MustRemove(data) + tt.Equal(t, 13, jp.N(2).N(0).First(result)) + tt.Equal(t, false, jp.N(2).N(1).Has(result)) + tt.Equal(t, false, jp.N(2).N(2).Has(result)) + data = indexedData() + result = x.MustRemoveOne(data) + tt.Equal(t, 12, jp.N(2).N(0).First(result)) + tt.Equal(t, 13, jp.N(2).N(1).First(result)) + tt.Equal(t, false, jp.N(2).N(2).Has(result)) + + x = jp.MustParseString("$[2][-2:-1]") // last two + data = indexedData() + result = x.MustRemove(data) + tt.Equal(t, 11, jp.N(2).N(0).First(result)) + tt.Equal(t, false, jp.N(2).N(1).Has(result)) + tt.Equal(t, false, jp.N(2).N(2).Has(result)) + data = indexedData() + result = x.MustRemoveOne(data) + tt.Equal(t, 11, jp.N(2).N(0).First(result)) + tt.Equal(t, 13, jp.N(2).N(1).First(result)) + tt.Equal(t, false, jp.N(2).N(2).Has(result)) + + x = jp.MustParseString("$[2][-999:999]") // out of bounds + data = indexedData() + result = x.MustRemove(data) + tt.Equal(t, 11, jp.N(2).N(0).First(result)) + tt.Equal(t, 12, jp.N(2).N(1).First(result)) + tt.Equal(t, 13, jp.N(2).N(2).First(result)) + data = indexedData() + result = x.MustRemoveOne(data) + tt.Equal(t, 11, jp.N(2).N(0).First(result)) + tt.Equal(t, 12, jp.N(2).N(1).First(result)) + tt.Equal(t, 13, jp.N(2).N(2).First(result)) +} + +func TestExprRemoveIndexedUnion(t *testing.T) { + x := jp.MustParseString("$[2][0,1]") + + data := indexedData() + result := x.MustRemove(data) + tt.Equal(t, 13, jp.N(2).N(0).First(result)) + tt.Equal(t, false, jp.N(2).N(1).Has(result)) + tt.Equal(t, false, jp.N(2).N(2).Has(result)) + + data = indexedData() + result = x.MustRemoveOne(data) + tt.Equal(t, 12, jp.N(2).N(0).First(result)) + tt.Equal(t, 13, jp.N(2).N(1).First(result)) + tt.Equal(t, false, jp.N(2).N(2).Has(result)) +} + +func TestExprRemoveIndexedWild(t *testing.T) { + x := jp.MustParseString("$[2][*]") + + data := indexedData() + result := x.MustRemove(data) + tt.Equal(t, false, jp.N(2).N(0).Has(result)) + tt.Equal(t, false, jp.N(2).N(1).Has(result)) + tt.Equal(t, false, jp.N(2).N(2).Has(result)) + + data = indexedData() + result = x.MustRemoveOne(data) + tt.Equal(t, 12, jp.N(2).N(0).First(result)) + tt.Equal(t, 13, jp.N(2).N(1).First(result)) + tt.Equal(t, false, jp.N(2).N(2).Has(result)) +} + +func TestExprRemoveKeyedChild(t *testing.T) { + x := jp.MustParseString("$.b") + + data := keyedData() + result := x.MustRemove(data) + tt.Equal(t, false, jp.C("b").Has(result)) +} + +func TestExprRemoveKeyedFilter(t *testing.T) { + x := jp.MustParseString("$.c[?@ > 11]") + + data := keyedData() + result := x.MustRemove(data) + tt.Equal(t, true, jp.C("c").C("c1").Has(result)) + tt.Equal(t, false, jp.C("c").C("c2").Has(result)) + tt.Equal(t, false, jp.C("c").C("c3").Has(result)) + + data = keyedData() + result = x.MustRemoveOne(data) + tt.Equal(t, true, jp.C("c").C("c1").Has(result)) + tt.Equal(t, false, jp.C("c").C("c2").Has(result)) + tt.Equal(t, true, jp.C("c").C("c3").Has(result)) +} + +func TestExprRemoveKeyedUnion(t *testing.T) { + x := jp.MustParseString("$.c['c1', 'c2']") + + data := keyedData() + result := x.MustRemove(data) + tt.Equal(t, false, jp.C("c").C("c1").Has(result)) + tt.Equal(t, false, jp.C("c").C("c2").Has(result)) + tt.Equal(t, true, jp.C("c").C("c3").Has(result)) + + data = keyedData() + result = x.MustRemoveOne(data) + tt.Equal(t, false, jp.C("c").C("c1").Has(result)) + tt.Equal(t, true, jp.C("c").C("c2").Has(result)) + tt.Equal(t, true, jp.C("c").C("c3").Has(result)) +} + +func TestExprRemoveKeyedWild(t *testing.T) { + x := jp.MustParseString("$.c[*]") + + data := keyedData() + result := x.MustRemove(data) + tt.Equal(t, false, jp.C("c").C("c1").Has(result)) + tt.Equal(t, false, jp.C("c").C("c2").Has(result)) + tt.Equal(t, false, jp.C("c").C("c3").Has(result)) + + data = keyedData() + result = x.MustRemoveOne(data) + tt.Equal(t, false, jp.C("c").C("c1").Has(result)) + tt.Equal(t, true, jp.C("c").C("c2").Has(result)) + tt.Equal(t, true, jp.C("c").C("c3").Has(result)) +} diff --git a/jp/slice.go b/jp/slice.go index bd24b04..f678b8e 100644 --- a/jp/slice.go +++ b/jp/slice.go @@ -138,6 +138,26 @@ func (f Slice) remove(value any) (out any, changed bool) { if changed { out = ns } + case Indexed: + size := tv.Size() + if start < 0 { + start = size + start + } + if end < 0 { + end = size + end + } + if size <= end { + end = size - 1 + } + if start < 0 || end < 0 || size <= start || size <= end || step == 0 { + return + } + for i := size - 1; 0 <= i; i-- { + if inStep(i, start, end, step) { + changed = true + tv.RemoveValueAtIndex(i) + } + } default: rv := reflect.ValueOf(value) if rv.Kind() == reflect.Slice { @@ -284,6 +304,27 @@ func (f Slice) removeOne(value any) (out any, changed bool) { if changed { out = ns } + case Indexed: + size := tv.Size() + if start < 0 { + start = size + start + } + if end < 0 { + end = size + end + } + if size <= end { + end = size - 1 + } + if start < 0 || end < 0 || size <= start || size <= end || step == 0 { + return + } + for i := 0; i < size; i++ { + if inStep(i, start, end, step) { + changed = true + tv.RemoveValueAtIndex(i) + break + } + } default: rv := reflect.ValueOf(value) if rv.Kind() == reflect.Slice { diff --git a/jp/union.go b/jp/union.go index 6a04bd1..b980ba1 100644 --- a/jp/union.go +++ b/jp/union.go @@ -127,6 +127,25 @@ func (f Union) removeOne(value any) (out any, changed bool) { } } } + case Indexed: + size := tv.Size() + for i := 0; i < size; i++ { + if f.hasN(int64(i)) { + tv.RemoveValueAtIndex(i) + changed = true + break + } + } + case Keyed: + keys := tv.Keys() + sort.Strings(keys) + for _, k := range keys { + if f.hasKey(k) { + tv.RemoveValueForKey(k) + changed = true + break + } + } default: rv := reflect.ValueOf(value) switch rv.Kind() { @@ -216,6 +235,22 @@ func (f Union) remove(value any) (out any, changed bool) { changed = true } } + case Indexed: + size := tv.Size() + for i := (size - 1); i >= 0; i-- { + if f.hasN(int64(i)) { + tv.RemoveValueAtIndex(i) + changed = true + } + } + case Keyed: + keys := tv.Keys() + for _, k := range keys { + if f.hasKey(k) { + tv.RemoveValueForKey(k) + changed = true + } + } default: rv := reflect.ValueOf(value) switch rv.Kind() { diff --git a/jp/wildcard.go b/jp/wildcard.go index fcb2636..f5d5edd 100644 --- a/jp/wildcard.go +++ b/jp/wildcard.go @@ -55,6 +55,20 @@ func (f Wildcard) remove(value any) (out any, changed bool) { delete(tv, k) } } + case Indexed: + size := tv.Size() + for i := (size - 1); i >= 0; i-- { + changed = true + tv.RemoveValueAtIndex(i) + } + case Keyed: + keys := tv.Keys() + if 0 < len(keys) { + changed = true + for _, k := range keys { + tv.RemoveValueForKey(k) + } + } default: rv := reflect.ValueOf(value) switch rv.Kind() { @@ -106,6 +120,18 @@ func (f Wildcard) removeOne(value any) (out any, changed bool) { sort.Strings(keys) delete(tv, keys[0]) } + case Indexed: + if 0 < tv.Size() { + changed = true + tv.RemoveValueAtIndex(0) + } + case Keyed: + keys := tv.Keys() + if 0 < len(keys) { + changed = true + sort.Strings(keys) + tv.RemoveValueForKey(keys[0]) + } default: rv := reflect.ValueOf(value) switch rv.Kind() { From b87f3fbf3d37d1c974a486c82cdca44a478a482c Mon Sep 17 00:00:00 2001 From: Skip Baney Date: Tue, 13 Aug 2024 13:06:59 -0600 Subject: [PATCH 3/3] Move RemoveValueAtIndex to separate interface --- jp/filter.go | 4 ++-- jp/indexed.go | 12 +++++++++--- jp/nth.go | 2 +- jp/slice.go | 4 ++-- jp/union.go | 4 ++-- jp/wildcard.go | 4 ++-- 6 files changed, 18 insertions(+), 12 deletions(-) diff --git a/jp/filter.go b/jp/filter.go index 28b6415..e22ab61 100644 --- a/jp/filter.go +++ b/jp/filter.go @@ -98,7 +98,7 @@ func (f Filter) remove(value any) (out any, changed bool) { changed = true } } - case Indexed: + case RemovableIndexed: size := tv.Size() for i := (size - 1); i >= 0; i-- { v := tv.ValueAtIndex(i) @@ -219,7 +219,7 @@ func (f Filter) removeOne(value any) (out any, changed bool) { } } } - case Indexed: + case RemovableIndexed: size := tv.Size() for i := 0; i < size; i++ { v := tv.ValueAtIndex(i) diff --git a/jp/indexed.go b/jp/indexed.go index bbe3c63..1e7b2ff 100644 --- a/jp/indexed.go +++ b/jp/indexed.go @@ -13,9 +13,15 @@ type Indexed interface { // SetValueAtIndex should set the value at the provided index. SetValueAtIndex(index int, value any) - // RemoveValueAtIndex removes an item from the collection. - RemoveValueAtIndex(index int) - // Size should return the size for the collection. Size() int } + +// RemovableIndexed describes an indexed collection that can remove items. +// Must be implemented to use [Expr.Remove]. +type RemovableIndexed interface { + Indexed + + // RemoveValueAtIndex removes an item from the collection. + RemoveValueAtIndex(index int) +} diff --git a/jp/nth.go b/jp/nth.go index e2ef04b..3d4d29c 100644 --- a/jp/nth.go +++ b/jp/nth.go @@ -59,7 +59,7 @@ func (f Nth) remove(value any) (out any, changed bool) { out = append(tv[:i], tv[i+1:]...) changed = true } - case Indexed: + case RemovableIndexed: size := tv.Size() if i < 0 { i = size + i diff --git a/jp/slice.go b/jp/slice.go index f678b8e..ce5b9d0 100644 --- a/jp/slice.go +++ b/jp/slice.go @@ -138,7 +138,7 @@ func (f Slice) remove(value any) (out any, changed bool) { if changed { out = ns } - case Indexed: + case RemovableIndexed: size := tv.Size() if start < 0 { start = size + start @@ -304,7 +304,7 @@ func (f Slice) removeOne(value any) (out any, changed bool) { if changed { out = ns } - case Indexed: + case RemovableIndexed: size := tv.Size() if start < 0 { start = size + start diff --git a/jp/union.go b/jp/union.go index b980ba1..e2df6e7 100644 --- a/jp/union.go +++ b/jp/union.go @@ -127,7 +127,7 @@ func (f Union) removeOne(value any) (out any, changed bool) { } } } - case Indexed: + case RemovableIndexed: size := tv.Size() for i := 0; i < size; i++ { if f.hasN(int64(i)) { @@ -235,7 +235,7 @@ func (f Union) remove(value any) (out any, changed bool) { changed = true } } - case Indexed: + case RemovableIndexed: size := tv.Size() for i := (size - 1); i >= 0; i-- { if f.hasN(int64(i)) { diff --git a/jp/wildcard.go b/jp/wildcard.go index f5d5edd..13325ec 100644 --- a/jp/wildcard.go +++ b/jp/wildcard.go @@ -55,7 +55,7 @@ func (f Wildcard) remove(value any) (out any, changed bool) { delete(tv, k) } } - case Indexed: + case RemovableIndexed: size := tv.Size() for i := (size - 1); i >= 0; i-- { changed = true @@ -120,7 +120,7 @@ func (f Wildcard) removeOne(value any) (out any, changed bool) { sort.Strings(keys) delete(tv, keys[0]) } - case Indexed: + case RemovableIndexed: if 0 < tv.Size() { changed = true tv.RemoveValueAtIndex(0)