Skip to content

Commit 6384383

Browse files
committed
Add an alternative strategy for joining pointers with big length
1 parent 9f2e856 commit 6384383

File tree

2 files changed

+52
-8
lines changed

2 files changed

+52
-8
lines changed

src/commonMain/kotlin/io/github/optimumcode/json/pointer/JsonPointer.kt

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,13 @@ public sealed class JsonPointer(
3131
*/
3232
public fun atIndex(index: Int): JsonPointer {
3333
require(index >= 0) { "negative index: $index" }
34-
return atProperty(index.toString())
34+
return insertLast(
35+
SegmentPointer(
36+
propertyName = index.toString(),
37+
depth = 1,
38+
index = index,
39+
),
40+
)
3541
}
3642

3743
/**
@@ -42,7 +48,10 @@ public sealed class JsonPointer(
4248
* val pointer = JsonPointer.ROOT.atProperty("prop1").atProperty("prop2") // "/prop1/prop2"
4349
* ```
4450
*/
45-
public fun atProperty(property: String): JsonPointer = insertLast(SegmentPointer(property))
51+
public fun atProperty(property: String): JsonPointer =
52+
insertLast(
53+
SegmentPointer(depth = 1, propertyName = property),
54+
)
4655

4756
override fun toString(): String {
4857
val str = asString
@@ -68,7 +77,33 @@ public sealed class JsonPointer(
6877
if (this !is SegmentPointer) {
6978
return last
7079
}
71-
return insertLastDeepCopy(this, last)
80+
if (depth < MAX_POINTER_DEPTH_FOR_RECURSIVE_INSERT) {
81+
return insertLastDeepCopy(this, last)
82+
}
83+
// avoid recursion when pointer depth is greater than a specified limit
84+
// this should help with avoiding stack-overflow error
85+
// when this method called for a pointer that has too many segments
86+
//
87+
// Using queue is less efficient than recursion (around 10%) but saves us from crash
88+
val queue = ArrayDeque<SegmentPointer>(depth)
89+
var cur: JsonPointer = this
90+
while (cur is SegmentPointer) {
91+
queue.add(cur)
92+
cur = cur.next
93+
}
94+
val additionalDepth = last.depth
95+
var result = last
96+
while (queue.isNotEmpty()) {
97+
val segment = queue.removeLast()
98+
result =
99+
SegmentPointer(
100+
propertyName = segment.propertyName,
101+
depth = segment.depth + additionalDepth,
102+
index = segment.index,
103+
next = result,
104+
)
105+
}
106+
return result
72107
}
73108

74109
// there might be an issue with stack in case this function is called deep on the stack
@@ -77,15 +112,18 @@ public sealed class JsonPointer(
77112
last: SegmentPointer,
78113
): JsonPointer =
79114
with(pointer) {
115+
val additionalDepth = last.depth
80116
if (next is SegmentPointer) {
81117
SegmentPointer(
82118
propertyName = propertyName,
119+
depth = depth + additionalDepth,
83120
index = index,
84121
next = insertLastDeepCopy(next, last),
85122
)
86123
} else {
87124
SegmentPointer(
88125
propertyName = propertyName,
126+
depth = depth + additionalDepth,
89127
index = index,
90128
next = last,
91129
)
@@ -145,6 +183,7 @@ public sealed class JsonPointer(
145183
}
146184

147185
public companion object {
186+
private const val MAX_POINTER_DEPTH_FOR_RECURSIVE_INSERT = 20
148187
internal const val SEPARATOR: Char = '/'
149188
internal const val QUOTATION: Char = '~'
150189
internal const val QUOTATION_ESCAPE: Char = '0'
@@ -187,13 +226,15 @@ public sealed class JsonPointer(
187226
lastSegment: SegmentPointer,
188227
parent: PointerParent?,
189228
): JsonPointer {
229+
var depth = lastSegment.depth
190230
var curr = lastSegment
191231
var parentValue = parent
192232
while (parentValue != null) {
193233
curr =
194234
parentValue.run {
195235
SegmentPointer(
196236
segment,
237+
++depth,
197238
curr,
198239
)
199240
}
@@ -283,6 +324,7 @@ internal object EmptyPointer : JsonPointer()
283324

284325
internal class SegmentPointer(
285326
val propertyName: String,
327+
val depth: Int = 1,
286328
override val next: JsonPointer = EmptyPointer,
287329
val index: Int = parseIndex(propertyName),
288330
) : JsonPointer(next) {

src/commonTest/kotlin/io/github/optimumcode/json/pointer/JsonPointerTest.kt

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class JsonPointerTest : FunSpec() {
4949
test("extracts segment path") {
5050
val pointer = JsonPointer("/first/second")
5151
assertSoftly {
52-
pointer.assertSegment(property = "first")
52+
pointer.assertSegment(property = "first", depth = 2)
5353
val next = pointer.next
5454
next shouldNotBe null
5555
next!!.assertSegment(property = "second")
@@ -68,7 +68,7 @@ class JsonPointerTest : FunSpec() {
6868

6969
test("parses several escaped characters '$escaped' as '$actual'") {
7070
val pointer = JsonPointer("/${escaped}and$escaped/test")
71-
pointer.assertSegment("${actual}and$actual")
71+
pointer.assertSegment("${actual}and$actual", depth = 2)
7272
pointer.next.apply {
7373
shouldNotBe(null)
7474
this!!.assertSegment("test")
@@ -84,7 +84,7 @@ class JsonPointerTest : FunSpec() {
8484
test("empty segment in the end") {
8585
val pointer = JsonPointer("/test/")
8686
assertSoftly {
87-
pointer.assertSegment(property = "test")
87+
pointer.assertSegment(property = "test", depth = 2)
8888
pointer.next
8989
.shouldNotBeNull()
9090
.assertSegment(property = "")
@@ -104,9 +104,9 @@ class JsonPointerTest : FunSpec() {
104104
test("empty segment in the middle") {
105105
val pointer = JsonPointer("/test1//test2")
106106
assertSoftly {
107-
pointer.assertSegment(property = "test1")
107+
pointer.assertSegment(property = "test1", depth = 3)
108108
var next = pointer.next.shouldNotBeNull()
109-
next.assertSegment(property = "")
109+
next.assertSegment(property = "", depth = 2)
110110
next = next.next.shouldNotBeNull()
111111
next.assertSegment(property = "test2")
112112
pointer.toString() shouldBe "/test1//test2"
@@ -150,12 +150,14 @@ class JsonPointerTest : FunSpec() {
150150
private fun JsonPointer.assertSegment(
151151
property: String,
152152
index: Int = -1,
153+
depth: Int = 1,
153154
) {
154155
asClue {
155156
this should beOfType<SegmentPointer>()
156157
this as SegmentPointer
157158
this.propertyName shouldBe property
158159
this.index shouldBe index
160+
this.depth shouldBe depth
159161
}
160162
}
161163
}

0 commit comments

Comments
 (0)