Skip to content

Commit 8afac53

Browse files
authored
fix: properly merge array values (#8)
1 parent 4448557 commit 8afac53

File tree

3 files changed

+153
-18
lines changed

3 files changed

+153
-18
lines changed

update_test.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
package astjson
22

33
import (
4+
"encoding/json"
5+
"fmt"
6+
"strings"
47
"testing"
8+
9+
"github.com/stretchr/testify/assert"
510
)
611

712
func TestObjectDelSet(t *testing.T) {
@@ -101,3 +106,109 @@ func TestValueDelSet(t *testing.T) {
101106
v.Set("x", MustParse(`[]`))
102107
v.SetArrayItem(1, MustParse(`[]`))
103108
}
109+
110+
func TestValue_AppendArrayItems(t *testing.T) {
111+
left := MustParse(`[1,2,3]`)
112+
right := MustParse(`[4,5,6]`)
113+
left.AppendArrayItems(right)
114+
if len(left.GetArray()) != 6 {
115+
t.Fatalf("unexpected length; got %d; want %d", len(left.GetArray()), 6)
116+
}
117+
out := left.MarshalTo(nil)
118+
if string(out) != `[1,2,3,4,5,6]` {
119+
t.Fatalf("unexpected output; got %q; want %q", out, `[1,2,3,4,5,6]`)
120+
}
121+
}
122+
123+
func TestMergeWithSwap(t *testing.T) {
124+
left := MustParse(`{"a":{"b":1,"c":2,"e":[],"f":[1],"h":[1,2,3]}}`)
125+
right := MustParse(`{"a":{"b":2,"d":3,"e":[1,2,3],"g":[1],"h":[4,5,6]}}`)
126+
out, _ := MergeValues(left, right)
127+
assert.Equal(t, `{"a":{"b":2,"c":2,"e":[1,2,3],"f":[1],"h":[4,5,6],"d":3,"g":[1]}}`, out.String())
128+
}
129+
130+
type RootObject struct {
131+
Child *ChildObject `json:"child"`
132+
}
133+
134+
type ChildObject struct {
135+
GrandChild *GrandChildObject `json:"grand_child"`
136+
}
137+
138+
type GrandChildObject struct {
139+
Items []string `json:"items"`
140+
}
141+
142+
func BenchmarkValue_SetArrayItem(b *testing.B) {
143+
144+
root := &RootObject{
145+
Child: &ChildObject{
146+
GrandChild: &GrandChildObject{
147+
Items: make([]string, 0),
148+
},
149+
},
150+
}
151+
152+
l, err := json.Marshal(root)
153+
assert.NoError(b, err)
154+
155+
root.Child.GrandChild.Items = make([]string, 1024*1024)
156+
157+
for i := 0; i < 1024*1024; i++ {
158+
root.Child.GrandChild.Items[i] = strings.Repeat("a", 1024)
159+
}
160+
161+
r, err := json.Marshal(root)
162+
assert.NoError(b, err)
163+
164+
b.ResetTimer()
165+
b.ReportAllocs()
166+
167+
for i := 0; i < b.N; i++ {
168+
l, _ := ParseBytesWithoutCache(l)
169+
r, _ := ParseBytesWithoutCache(r)
170+
out, _ := MergeValues(l, r)
171+
arr := out.GetArray("child", "grand_child", "items")
172+
assert.Len(b, arr, 1024*1024)
173+
}
174+
}
175+
176+
func BenchmarkMergeValuesWithATonOfRecursion(b *testing.B) {
177+
b.ReportAllocs()
178+
179+
left := MustParse(`{"a":{}}`)
180+
str := fmt.Sprintf(
181+
`{"ba":{"bb":{"bc":{"bd":[%s, %s, %s], "be": {"bf": %s}}}}}`,
182+
objectWithRecursion(10),
183+
objectWithRecursion(20),
184+
objectWithRecursion(2),
185+
objectWithRecursion(3))
186+
187+
expected := fmt.Sprintf(
188+
`{"a":{},"ba":{"bb":{"bc":{"bd":[%s,%s,%s],"be":{"bf":%s}}}}}`,
189+
objectWithRecursion(10),
190+
objectWithRecursion(20),
191+
objectWithRecursion(2),
192+
objectWithRecursion(3))
193+
194+
_ = expected
195+
196+
right := MustParse(str)
197+
198+
for i := 0; i < b.N; i++ {
199+
_, _ = MergeValues(left, right)
200+
/*v, _ := MergeValues(left, right)
201+
if v.String() != expected {
202+
assert.Equal(b, expected, v.String())
203+
}*/
204+
}
205+
206+
fmt.Printf("left: %s\n", left.String())
207+
}
208+
209+
func objectWithRecursion(depth int) string {
210+
if depth == 0 {
211+
return `{}`
212+
}
213+
return `{"a":` + objectWithRecursion(depth-1) + `}`
214+
}

util.go

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -42,30 +42,39 @@ func MergeValues(a, b *Value) (*Value, bool) {
4242
case TypeObject:
4343
ao, _ := a.Object()
4444
bo, _ := b.Object()
45-
ao.Visit(func(key []byte, l *Value) {
46-
sKey := b2s(key)
47-
r := bo.Get(sKey)
48-
if r == nil {
49-
return
45+
ao.unescapeKeys()
46+
bo.unescapeKeys()
47+
for i := range bo.kvs {
48+
k := bo.kvs[i].k
49+
r := bo.kvs[i].v
50+
l := ao.Get(k)
51+
if l == nil {
52+
ao.Set(k, r)
53+
continue
5054
}
51-
merged, changed := MergeValues(l, r)
55+
n, changed := MergeValues(l, r)
5256
if changed {
53-
ao.Set(b2s(key), merged)
57+
ao.Set(k, n)
5458
}
55-
})
56-
bo.Visit(func(key []byte, r *Value) {
57-
sKey := b2s(key)
58-
if ao.Get(sKey) != nil {
59-
return
60-
}
61-
ao.Set(sKey, r)
62-
})
59+
}
6360
return a, false
6461
case TypeArray:
6562
aa, _ := a.Array()
6663
ba, _ := b.Array()
67-
for i := 0; i < len(ba); i++ {
68-
a.SetArrayItem(len(aa)+i, ba[i])
64+
if len(aa) == 0 {
65+
return b, true
66+
}
67+
if len(ba) == 0 {
68+
return a, false
69+
}
70+
if len(aa) != len(ba) {
71+
return b, true
72+
}
73+
for i := range aa {
74+
n, changed := MergeValues(aa[i], ba[i])
75+
if changed {
76+
aa[i] = n
77+
}
6978
}
7079
return a, false
7180
case TypeFalse:
@@ -151,6 +160,13 @@ func ValueIsNonNull(v *Value) bool {
151160
return true
152161
}
153162

163+
func (v *Value) AppendArrayItems(right *Value) {
164+
if v.t != TypeArray || right.t != TypeArray {
165+
return
166+
}
167+
v.a = append(v.a, right.a...)
168+
}
169+
154170
func ValueIsNull(v *Value) bool {
155171
return !ValueIsNonNull(v)
156172
}

util_test.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,15 @@ func TestMergeValuesArray(t *testing.T) {
4646
merged, changed := MergeValues(a, b)
4747
require.Equal(t, false, changed)
4848
out := merged.MarshalTo(nil)
49-
require.Equal(t, `[1,2,3,4]`, string(out))
49+
require.Equal(t, `[3,4]`, string(out))
50+
}
51+
52+
func TestMergeObjectValuesArray(t *testing.T) {
53+
a, b := MustParse(`[{"a":1,"b":2},{"x":1}]`), MustParse(`[{"a":2,"b":3,"c":4},{"y":1}]`)
54+
merged, changed := MergeValues(a, b)
55+
require.Equal(t, false, changed)
56+
out := merged.MarshalTo(nil)
57+
require.Equal(t, `[{"a":2,"b":3,"c":4},{"x":1,"y":1}]`, string(out))
5058
}
5159

5260
func TestMergeValuesNestedObjects(t *testing.T) {

0 commit comments

Comments
 (0)