Skip to content

Commit 8424bbc

Browse files
committed
[*] fix: Fix some bug for Walk()
1 parent 382e03e commit 8424bbc

File tree

3 files changed

+116
-114
lines changed

3 files changed

+116
-114
lines changed

iteration.go

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package jsonvalue
22

3-
import "sort"
3+
import (
4+
"bytes"
5+
"fmt"
6+
"sort"
7+
)
48

59
// Deprecated: ObjectIter is a deprecated type.
610
type ObjectIter struct {
@@ -155,6 +159,36 @@ type PathItem struct {
155159
Key string // Key of the object, "" means this element is NOT an object
156160
}
157161

162+
func (p PathItem) String() string {
163+
if p.Idx < 0 {
164+
return p.Key
165+
}
166+
return fmt.Sprintf("[%d]", p.Idx)
167+
}
168+
169+
// Path is a list of PathItem
170+
type Path []PathItem
171+
172+
// Last returns last element of path
173+
func (p Path) Last() PathItem {
174+
le := len(p)
175+
if le == 0 {
176+
return PathItem{Idx: -1}
177+
}
178+
return p[le-1]
179+
}
180+
181+
func (p Path) String() string {
182+
bdr := bytes.Buffer{}
183+
for i, item := range p {
184+
if i > 0 {
185+
bdr.WriteByte('.')
186+
}
187+
bdr.WriteString(item.String())
188+
}
189+
return bdr.String()
190+
}
191+
158192
func appendPathIndex(items []PathItem, i int) []PathItem {
159193
return append(items, PathItem{Idx: i, Key: ""})
160194
}
@@ -168,7 +202,7 @@ func appendPathKey(items []PathItem, key string) []PathItem {
168202
//
169203
// WalkFunc 是一个回调函数类型,用于遍历一个 JSON 值的所有子成员。返回 true 表示继续迭代,
170204
// 返回 false 表示退出迭代
171-
type WalkFunc func(path []PathItem, v *V) bool
205+
type WalkFunc func(path Path, v *V) bool
172206

173207
// Walk walks through all sub values of a JSON value.
174208
//
@@ -180,19 +214,21 @@ func (v *V) Walk(fn WalkFunc) {
180214
v.walk(nil, fn)
181215
}
182216

183-
func (v *V) walk(parent []PathItem, fn WalkFunc) bool {
217+
func (v *V) walk(parent Path, fn WalkFunc) bool {
218+
shouldContinue := true
184219
switch v.ValueType() {
185220
case Object:
186-
v.RangeObjects(func(k string, v *V) bool {
187-
return v.walk(appendPathKey(parent, k), fn)
221+
v.RangeObjects(func(k string, sub *V) bool {
222+
shouldContinue = sub.walk(appendPathKey(parent, k), fn)
223+
return shouldContinue
188224
})
189-
return true
190225
case Array:
191-
v.RangeArray(func(i int, v *V) bool {
192-
return v.walk(appendPathIndex(parent, i), fn)
226+
v.RangeArray(func(i int, sub *V) bool {
227+
shouldContinue = sub.walk(appendPathIndex(parent, i), fn)
228+
return shouldContinue
193229
})
194-
return true
195230
default:
196-
return fn(parent, v)
231+
shouldContinue = fn(parent, v)
197232
}
233+
return shouldContinue
198234
}

iteration_test.go

Lines changed: 50 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ func testIteration(t *testing.T) {
1010
cv("Range Array", func() { testRangeArray(t) })
1111
cv("Range Object", func() { testRangeObject(t) })
1212
cv("Range Object by seq", func() { testRangeObjectsBySetSequence(t) })
13-
cv("Walk()", func() { testWalk(t) })
13+
cv("Walk", func() { testWalk(t) })
1414
}
1515

1616
func testRangeArray(t *testing.T) {
@@ -266,7 +266,7 @@ func testWalkBasicTypes(t *testing.T) {
266266
cv("string value", func() {
267267
v := NewString("hello")
268268
walkCount := 0
269-
v.Walk(func(path []PathItem, val *V) bool {
269+
v.Walk(func(path Path, val *V) bool {
270270
walkCount++
271271
so(len(path), eq, 0) // Root level, no path
272272
so(val.ValueType(), eq, String)
@@ -280,9 +280,12 @@ func testWalkBasicTypes(t *testing.T) {
280280
cv("number value", func() {
281281
v := NewInt64(42)
282282
walkCount := 0
283-
v.Walk(func(path []PathItem, val *V) bool {
283+
v.Walk(func(path Path, val *V) bool {
284284
walkCount++
285+
lastItem := path.Last()
285286
so(len(path), eq, 0)
287+
so(lastItem.Idx, eq, -1)
288+
so(lastItem.Key, eq, "")
286289
so(val.ValueType(), eq, Number)
287290
so(val.Int64(), eq, 42)
288291
return true
@@ -294,7 +297,7 @@ func testWalkBasicTypes(t *testing.T) {
294297
cv("boolean value", func() {
295298
v := NewBool(true)
296299
walkCount := 0
297-
v.Walk(func(path []PathItem, val *V) bool {
300+
v.Walk(func(path Path, val *V) bool {
298301
walkCount++
299302
so(len(path), eq, 0)
300303
so(val.ValueType(), eq, Boolean)
@@ -308,7 +311,7 @@ func testWalkBasicTypes(t *testing.T) {
308311
cv("null value", func() {
309312
v := NewNull()
310313
walkCount := 0
311-
v.Walk(func(path []PathItem, val *V) bool {
314+
v.Walk(func(path Path, val *V) bool {
312315
walkCount++
313316
so(len(path), eq, 0)
314317
so(val.ValueType(), eq, Null)
@@ -324,7 +327,7 @@ func testWalkEmptyContainers(t *testing.T) {
324327
cv("empty object", func() {
325328
v := NewObject()
326329
walkCount := 0
327-
v.Walk(func(path []PathItem, val *V) bool {
330+
v.Walk(func(path Path, val *V) bool {
328331
walkCount++
329332
t.Errorf("Should not walk through empty object")
330333
return true
@@ -336,7 +339,7 @@ func testWalkEmptyContainers(t *testing.T) {
336339
cv("empty array", func() {
337340
v := NewArray()
338341
walkCount := 0
339-
v.Walk(func(path []PathItem, val *V) bool {
342+
v.Walk(func(path Path, val *V) bool {
340343
walkCount++
341344
t.Errorf("Should not walk through empty array")
342345
return true
@@ -354,7 +357,7 @@ func testWalkSimpleObject(t *testing.T) {
354357

355358
expected := map[string]struct {
356359
valueType ValueType
357-
value interface{}
360+
value any
358361
}{
359362
"greeting": {String, "hello"},
360363
"number": {Number, int64(123)},
@@ -363,7 +366,7 @@ func testWalkSimpleObject(t *testing.T) {
363366
}
364367

365368
walkCount := 0
366-
v.Walk(func(path []PathItem, val *V) bool {
369+
v.Walk(func(path Path, val *V) bool {
367370
walkCount++
368371
so(len(path), eq, 1)
369372

@@ -403,7 +406,7 @@ func testWalkSimpleArray(t *testing.T) {
403406

404407
expectedValues := []struct {
405408
valueType ValueType
406-
value interface{}
409+
value any
407410
}{
408411
{String, "first"},
409412
{Number, int64(456)},
@@ -412,7 +415,7 @@ func testWalkSimpleArray(t *testing.T) {
412415
}
413416

414417
walkCount := 0
415-
v.Walk(func(path []PathItem, val *V) bool {
418+
v.Walk(func(path Path, val *V) bool {
416419
so(len(path), eq, 1)
417420

418421
idx := path[0].Idx
@@ -459,7 +462,7 @@ func testWalkNestedObjects(t *testing.T) {
459462
v.MustSet(level1).At("level1")
460463

461464
walkCount := 0
462-
v.Walk(func(path []PathItem, val *V) bool {
465+
v.Walk(func(path Path, val *V) bool {
463466
walkCount++
464467

465468
if len(path) == 3 {
@@ -490,7 +493,7 @@ func testWalkNestedArrays(t *testing.T) {
490493
v.MustAppend(level1).InTheEnd()
491494

492495
walkCount := 0
493-
v.Walk(func(path []PathItem, val *V) bool {
496+
v.Walk(func(path Path, val *V) bool {
494497
walkCount++
495498

496499
if len(path) == 3 {
@@ -531,24 +534,14 @@ func testWalkMixedNesting(t *testing.T) {
531534
v.MustSet(arr).At("array")
532535
v.MustSetString("value").At("simple")
533536

534-
walkResults := make(map[string]interface{})
537+
walkResults := make(map[string]any)
535538
walkCount := 0
536539

537-
v.Walk(func(path []PathItem, val *V) bool {
540+
v.Walk(func(path Path, val *V) bool {
538541
walkCount++
539542

540-
// Create a path string for identification
541-
pathStr := ""
542-
for i, p := range path {
543-
if i > 0 {
544-
pathStr += "."
545-
}
546-
if p.Key != "" {
547-
pathStr += p.Key
548-
} else {
549-
pathStr += fmt.Sprintf("[%d]", p.Idx)
550-
}
551-
}
543+
// Create a path string for identification using Path.String()
544+
pathStr := path.String()
552545

553546
if val.ValueType() == String {
554547
walkResults[pathStr] = val.String()
@@ -576,7 +569,7 @@ func testWalkPathValidation(t *testing.T) {
576569

577570
pathValidations := make(map[string]bool)
578571

579-
v.Walk(func(path []PathItem, val *V) bool {
572+
v.Walk(func(path Path, val *V) bool {
580573
pathStr := ""
581574
for _, p := range path {
582575
if p.Key != "" {
@@ -632,35 +625,22 @@ func testWalkComplexStructure(t *testing.T) {
632625
v, err := UnmarshalString(raw)
633626
so(err, isNil)
634627

635-
leafValues := make(map[string]interface{})
628+
leafValues := make(map[string]any)
636629
walkCount := 0
637630

638-
v.Walk(func(path []PathItem, val *V) bool {
631+
v.Walk(func(path Path, val *V) bool {
639632
walkCount++
640633

641-
// Build path string
642-
pathStr := ""
643-
for i, p := range path {
644-
if i > 0 {
645-
pathStr += "."
646-
}
647-
if p.Key != "" {
648-
pathStr += p.Key
649-
} else {
650-
pathStr += fmt.Sprintf("[%d]", p.Idx)
651-
}
652-
}
653-
654634
// Store leaf values
655635
switch val.ValueType() {
656636
case String:
657-
leafValues[pathStr] = val.String()
637+
leafValues[path.String()] = val.String()
658638
case Number:
659-
leafValues[pathStr] = val.Int64()
639+
leafValues[path.String()] = val.Int64()
660640
case Boolean:
661-
leafValues[pathStr] = val.Bool()
641+
leafValues[path.String()] = val.Bool()
662642
case Null:
663-
leafValues[pathStr] = nil
643+
leafValues[path.String()] = nil
664644
}
665645

666646
return true
@@ -692,7 +672,7 @@ func testWalkEarlyTermination(t *testing.T) {
692672
v.MustAppendString("fourth").InTheEnd()
693673

694674
walkCount := 0
695-
v.Walk(func(path []PathItem, val *V) bool {
675+
v.Walk(func(path Path, val *V) bool {
696676
walkCount++
697677
// Stop at the second element
698678
return walkCount < 2
@@ -711,7 +691,7 @@ func testWalkEarlyTermination(t *testing.T) {
711691

712692
walkCount := 0
713693
var lastKey string
714-
v.Walk(func(path []PathItem, val *V) bool {
694+
v.Walk(func(path Path, val *V) bool {
715695
walkCount++
716696
lastKey = path[0].Key
717697
// Stop after first element
@@ -724,42 +704,29 @@ func testWalkEarlyTermination(t *testing.T) {
724704

725705
// Test early termination in nested structure
726706
cv("early termination in nested structure", func() {
727-
v := NewObject()
728-
729-
// Create a nested structure with multiple elements
730-
arr := NewArray()
731-
arr.MustAppendString("item1").InTheEnd()
732-
arr.MustAppendString("item2").InTheEnd()
733-
arr.MustAppendString("item3").InTheEnd()
734-
v.MustSet(arr).At("array")
735-
736-
v.MustSetString("simple_value").At("simple")
737-
v.MustSetInt64(42).At("number")
738-
739-
walkCount := 0
740-
terminatedAt := ""
741-
v.Walk(func(path []PathItem, val *V) bool {
742-
walkCount++
707+
// Use array structure to ensure deterministic iteration order
708+
v := NewArray()
709+
v.MustAppendString("item1").InTheEnd()
710+
v.MustAppendString("item2").InTheEnd()
711+
v.MustAppendString("item3").InTheEnd()
712+
v.MustAppendString("item4").InTheEnd()
713+
v.MustAppendString("item5").InTheEnd()
714+
715+
// This structure has 5 leaf nodes with deterministic iteration order
716+
totalWalkCount := 0
717+
lastValue := ""
718+
719+
v.Walk(func(path Path, v *V) bool {
720+
totalWalkCount++
743721
// Build path string for debugging
744-
pathStr := ""
745-
for i, p := range path {
746-
if i > 0 {
747-
pathStr += "."
748-
}
749-
if p.Key != "" {
750-
pathStr += p.Key
751-
} else {
752-
pathStr += fmt.Sprintf("[%d]", p.Idx)
753-
}
754-
}
755-
terminatedAt = pathStr
722+
lastValue = v.String()
756723

757-
// Stop after third element to allow some iteration
758-
return walkCount < 4
724+
// Stop after fourth element to allow some iteration
725+
return path.Last().Idx < 3
759726
})
760727

761-
so(walkCount, eq, 4) // Should have stopped after 4 elements
762-
so(terminatedAt, ne, "") // Should have captured termination point
728+
so(totalWalkCount, eq, 4)
729+
so(lastValue, eq, "item4")
763730
})
764731

765732
// Test that returning true continues iteration normally
@@ -770,7 +737,7 @@ func testWalkEarlyTermination(t *testing.T) {
770737
v.MustAppendString("c").InTheEnd()
771738

772739
walkCount := 0
773-
v.Walk(func(path []PathItem, val *V) bool {
740+
v.Walk(func(path Path, val *V) bool {
774741
walkCount++
775742
return true // Always continue
776743
})
@@ -788,7 +755,7 @@ func testWalkEarlyTermination(t *testing.T) {
788755

789756
walkCount := 0
790757
foundBanana := false
791-
v.Walk(func(path []PathItem, val *V) bool {
758+
v.Walk(func(path Path, val *V) bool {
792759
walkCount++
793760
if val.String() == "banana" {
794761
foundBanana = true

0 commit comments

Comments
 (0)