Skip to content

Commit 749d1f7

Browse files
committed
implement recursive wildcard
1 parent 0e4efd0 commit 749d1f7

File tree

5 files changed

+103
-7
lines changed

5 files changed

+103
-7
lines changed

src/main/kotlin/com/nfeld/jsonpathkt/PathCompiler.kt

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,13 @@ internal object PathCompiler {
2727

2828
fun addObjectAccessorToken() {
2929
val key = keyBuilder.toString()
30-
when {
31-
isDeepScan -> tokens.add(DeepScanObjectAccessorToken(listOf(key)))
32-
isWildcard -> tokens.add(WildcardToken())
33-
else -> tokens.add(ObjectAccessorToken(key))
30+
val token = when {
31+
isDeepScan && isWildcard -> DeepScanWildcardToken()
32+
isDeepScan -> DeepScanObjectAccessorToken(listOf(key))
33+
isWildcard -> WildcardToken()
34+
else -> ObjectAccessorToken(key)
3435
}
36+
tokens.add(token)
3537
}
3638

3739
val len = path.length
@@ -40,6 +42,9 @@ internal object PathCompiler {
4042
val c = path[i]
4143
val next = path.getOrNull(i + 1)
4244
when {
45+
c == '*' && isDeepScan -> {
46+
isWildcard = true
47+
}
4348
c == '.' -> {
4449
if (keyBuilder.isNotEmpty() || isWildcard) {
4550
addObjectAccessorToken()
@@ -68,6 +73,7 @@ internal object PathCompiler {
6873
val token = compileBracket(path, i, closingBracketIndex)
6974
if (isDeepScan) {
7075
val deepScanToken: Token? = when (token) {
76+
is WildcardToken -> DeepScanWildcardToken()
7177
is ObjectAccessorToken -> DeepScanObjectAccessorToken(listOf(token.key))
7278
is MultiObjectAccessorToken -> DeepScanObjectAccessorToken(token.keys)
7379
is ArrayAccessorToken -> DeepScanArrayAccessorToken(listOf(token.index))

src/main/kotlin/com/nfeld/jsonpathkt/Token.kt

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -352,9 +352,8 @@ internal data class DeepScanLengthBasedArrayAccessorToken(val startIndex: Int,
352352
}
353353
is ArrayNode -> {
354354
ArrayLengthBasedRangeAccessorToken(startIndex, endIndex, offsetFromEnd)
355-
?.read(node)
356-
?.let { resultAny ->
357-
val resultArray = resultAny as? ArrayNode
355+
.read(node)?.let { resultNode ->
356+
val resultArray = resultNode as? ArrayNode
358357
resultArray?.forEach { result.add(it) }
359358
}
360359

@@ -430,6 +429,51 @@ internal class WildcardToken : Token {
430429
override fun equals(other: Any?): Boolean = other is WildcardToken
431430
}
432431

432+
internal class DeepScanWildcardToken : Token {
433+
private fun scan(node: JsonNode, result: ArrayNode) {
434+
when (node) {
435+
is RootLevelArrayNode -> {
436+
// no need to add anything on root level, scan down next level
437+
node.forEach {
438+
if (it.isNotNullOrMissing()) {
439+
scan(it, result)
440+
}
441+
}
442+
}
443+
is ObjectNode,
444+
is ArrayNode -> {
445+
WildcardToken().read(node)?.let {
446+
if (it is ArrayNode) {
447+
it.forEach {
448+
if (it.isNotNullOrMissing()) {
449+
result.add(it)
450+
}
451+
}
452+
}
453+
}
454+
455+
// now recursively scan underlying objects/arrays
456+
node.forEach {
457+
if (it.isNotNullOrMissing()) {
458+
scan(it, result)
459+
}
460+
}
461+
}
462+
else -> {}
463+
}
464+
}
465+
466+
override fun read(json: JsonNode): JsonNode? {
467+
val result = RootLevelArrayNode()
468+
scan(json, result)
469+
return result
470+
}
471+
472+
override fun toString(): String = "DeepScanWildcardToken"
473+
override fun hashCode(): Int = toString().hashCode()
474+
override fun equals(other: Any?): Boolean = other is DeepScanWildcardToken
475+
}
476+
433477
interface Token {
434478
/**
435479
* Takes in JsonNode and outputs next JsonNode or value by evaluating token against current object/array in path

src/test/kotlin/com/nfeld/jsonpathkt/JsonPathTest.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,16 @@ class JsonPathTest : DescribeSpec({
521521
JsonPath.parse("""[[1], [2,3]]""")!!.read<JsonNode>("$.*").toString() shouldBe """[[1],[2,3]]"""
522522
JsonPath.parse("""[[1], [2,3]]""")!!.read<JsonNode>("$.*.*").toString() shouldBe """[1,2,3]"""
523523
JsonPath.parse("""[[1], [2,3]]""")!!.read<JsonNode>("$.*.*.*").toString() shouldBe """[]"""
524+
525+
JsonPath.parse("""[1,[2],[3,4],[5,6,7,[8,9,10,11]]]""")!!.read<JsonNode>("$.*").toString() shouldBe """[1,[2],[3,4],[5,6,7,[8,9,10,11]]]"""
526+
JsonPath.parse("""[1,[2],[3,4],[5,6,7,[8,9,10,11]]]""")!!.read<JsonNode>("$.*.*").toString() shouldBe """[2,3,4,5,6,7,[8,9,10,11]]"""
527+
JsonPath.parse("""[1,[2],[3,4],[5,6,7,[8,9,10,11]]]""")!!.read<JsonNode>("$.*.*.*").toString() shouldBe """[8,9,10,11]"""
528+
JsonPath.parse("""[1,[2],[3,4],[5,6,7,[8,9,10,11]]]""")!!.read<JsonNode>("$.*.*.*.*").toString() shouldBe """[]"""
529+
530+
JsonPath.parse("""[1,[2],[3,4],[5,6,7,[8,9,10,11]]]""")!!.read<JsonNode>("$..*").toString() shouldBe """[1,[2],[3,4],[5,6,7,[8,9,10,11]],2,3,4,5,6,7,[8,9,10,11],8,9,10,11]"""
531+
JsonPath.parse("""[1,[2],[3,4],[5,6,7,[8,9,10,11]]]""")!!.read<JsonNode>("$..[*]").toString() shouldBe """[1,[2],[3,4],[5,6,7,[8,9,10,11]],2,3,4,5,6,7,[8,9,10,11],8,9,10,11]"""
532+
JsonPath.parse("""[1,[2],[3,4],[5,6,7,[8,9,10,11]]]""")!!.read<JsonNode>("$..*.*").toString() shouldBe """[2,3,4,5,6,7,[8,9,10,11],8,9,10,11]"""
533+
JsonPath.parse("""[1,[2],[3,4],[5,6,7,[8,9,10,11]]]""")!!.read<JsonNode>("$..*..*").toString() shouldBe """[2,3,4,5,6,7,[8,9,10,11],8,9,10,11,8,9,10,11]"""
524534
}
525535

526536
it("should handle lists properly") {

src/test/kotlin/com/nfeld/jsonpathkt/PathCompilerTest.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ class PathCompilerTest : StringSpec({
4747
assertEquals(listOf(WildcardToken(), DeepScanObjectAccessorToken(listOf("key"))), f("$.*..key"))
4848
assertEquals(listOf(WildcardToken(), DeepScanArrayAccessorToken(listOf(1,2,3))), f("$.*..[1:4]"))
4949
f("""$..["key"]""") shouldBe listOf(DeepScanObjectAccessorToken(listOf("key")))
50+
f("$..*") shouldBe listOf(DeepScanWildcardToken())
51+
f("$..[*]") shouldBe listOf(DeepScanWildcardToken())
52+
f("$..*..*") shouldBe listOf(DeepScanWildcardToken(), DeepScanWildcardToken())
53+
f("$..[*]..[*]") shouldBe listOf(DeepScanWildcardToken(), DeepScanWildcardToken())
5054
}
5155

5256
"should compile without root $ token" {

src/test/kotlin/com/nfeld/jsonpathkt/TokenTest.kt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,5 +367,37 @@ class TokenTest : DescribeSpec({
367367
WildcardToken().equals(RootLevelArrayNode()) shouldBe false
368368
}
369369
}
370+
371+
describe("DeepScanWildcardToken") {
372+
it("should handle empty cases") {
373+
WildcardToken().read(createArrayNode()).toString() shouldBe """[]"""
374+
WildcardToken().read(createObjectNode()).toString() shouldBe """[]"""
375+
}
376+
377+
it("should get values from objects and strip") {
378+
val objectNode = readTree("""{ "some": "string", "int": 42, "object": { "key": "value" }, "array": [0, 1] }""")
379+
WildcardToken().read(objectNode).toString() shouldBe """["string",42,{"key":"value"},[0,1]]"""
380+
}
381+
382+
it("should return a RootLevelArrayNode if root list replaced with another list before modifying values") {
383+
val arrayNode = readTree("""["string", 42, { "key": "value" }, [0, 1] ]""")
384+
WildcardToken().read(arrayNode).toString() shouldBe """["string",42,{"key":"value"},[0,1]]"""
385+
}
386+
387+
it("should drop scalars and move everything down on root RootLevelArrayNode") {
388+
val arrayNode = readTree("""["string", 42, { "key": "value" }, [0, 1] ]""")
389+
val res1 = WildcardToken().read(arrayNode)
390+
(res1 is RootLevelArrayNode) shouldBe true
391+
val res2 = WildcardToken().read(res1!!)
392+
res2.toString() shouldBe """["value",0,1]"""
393+
}
394+
395+
it("should override toString, hashCode, and equals") {
396+
WildcardToken().toString() shouldBe "WildcardToken"
397+
WildcardToken().hashCode() shouldBe "WildcardToken".hashCode()
398+
WildcardToken().equals(WildcardToken()) shouldBe true
399+
WildcardToken().equals(RootLevelArrayNode()) shouldBe false
400+
}
401+
}
370402
}
371403
})

0 commit comments

Comments
 (0)