1 /* 2 * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. 3 */ 4 5 package kotlinx.serialization 6 7 import kotlinx.serialization.json.* 8 import kotlinx.serialization.test.* 9 import kotlin.test.* 10 11 class JsonPathTest : JsonTestBase() { 12 13 @Serializable 14 class Outer(val a: Int, val i: Inner) 15 16 @Serializable 17 class Inner(val a: Int, val b: String, val c: List<String>, val d: Map<Int, Box>) 18 19 @Serializable 20 class Box(val s: String) 21 22 @Test testBasicErrornull23 fun testBasicError() { 24 expectPath("$.a") { Json.decodeFromString<Outer>("""{"a":foo}""") } 25 expectPath("$.i") { Json.decodeFromString<Outer>("""{"a":42, "i":[]}""") } 26 expectPath("$.i.b") { Json.decodeFromString<Outer>("""{"a":42, "i":{"a":43, "b":42}""") } 27 expectPath("$.i.b") { Json.decodeFromString<Outer>("""{"a":42, "i":{"b":42}""") } 28 } 29 30 @Test testMissingKeynull31 fun testMissingKey() { 32 expectPath("$.i.d['1']") { Json.decodeFromString<Outer>("""{"a":42, "i":{"d":{1:{}}""") } 33 } 34 35 @Test testUnknownKeyIsProperlyReportednull36 fun testUnknownKeyIsProperlyReported() { 37 expectPath("$.i") { Json.decodeFromString<Outer>("""{"a":42, "i":{"foo":42}""") } 38 expectPath("$") { Json.decodeFromString<Outer>("""{"x":{}, "a": 42}""") } 39 // The only place we have misattribution in 40 // Json.decodeFromString<Outer>("""{"a":42, "x":{}}""") 41 } 42 43 @Test testMalformedRootObjectnull44 fun testMalformedRootObject() { 45 expectPath("$") { Json.decodeFromString<Outer>("""{{""") } 46 } 47 48 @Test testArrayIndexnull49 fun testArrayIndex() { 50 expectPath("$.i.c[1]") { Json.decodeFromString<Outer>("""{"a":42, "i":{ "c": ["a", 2]}""") } 51 expectPath("$[2]") { Json.decodeFromString<List<String>>("""["a", "2", 3]""") } 52 } 53 54 @Test testArrayIndexMalformedArraynull55 fun testArrayIndexMalformedArray() { 56 // Also zeroes as we cannot distinguish what exactly wen wrong is such cases 57 expectPath("$.i.c[0]") { Json.decodeFromString<Outer>("""{"a":42, "i":{ "c": [[""") } 58 expectPath("$[0]") { Json.decodeFromString<List<String>>("""[[""") } 59 // But we can here 60 expectPath("$.i.c\n") { Json.decodeFromString<Outer>("""{"a":42, "i":{ "c": {}}}""") } 61 expectPath("$\n") { Json.decodeFromString<List<String>>("""{""") } 62 } 63 64 @Test testMapKeynull65 fun testMapKey() { 66 expectPath("$.i.d\n") { Json.decodeFromString<Outer>("""{"a":42, "i":{ "d": {"foo": {}}""") } 67 expectPath("$.i.d\n") { Json.decodeFromString<Outer>("""{"a":42, "i":{ "d": {42: {"s":"s"}, 42.0:{}}""") } 68 expectPath("$\n") { Json.decodeFromString<Map<Int, String>>("""{"foo":"bar"}""") } 69 expectPath("$\n") { Json.decodeFromString<Map<Int, String>>("""{42:"bar", "foo":"bar"}""") } 70 expectPath("$['42']['foo']") { Json.decodeFromString<Map<Int, Map<String, Int>>>("""{42: {"foo":"bar"}""") } 71 } 72 73 @Test testMalformedMapnull74 fun testMalformedMap() { 75 expectPath("$.i.d\n") { Json.decodeFromString<Outer>("""{"a":42, "i":{ "d": []""") } 76 expectPath("$\n") { Json.decodeFromString<Map<Int, String>>("""[]""") } 77 } 78 79 @Test testMapValuenull80 fun testMapValue() { 81 expectPath("$.i.d['42']\n") { Json.decodeFromString<Outer>("""{"a":42, "i":{ "d": {42: {"xx":"bar"}}""") } 82 expectPath("$.i.d['43']\n") { Json.decodeFromString<Outer>("""{"a":42, "i":{ "d": {42: {"s":"s"}, 43: {"xx":"bar"}}}""") } 83 expectPath("$['239']") { Json.decodeFromString<Map<Int, String>>("""{239:bar}""") } 84 } 85 86 @Serializable 87 class Fp(val d: Double) 88 89 @Test testInvalidFpnull90 fun testInvalidFp() { 91 expectPath("$.d") { Json.decodeFromString<Fp>("""{"d": NaN}""") } 92 } 93 94 @Serializable 95 class EH(val e: E) 96 enum class E 97 98 @Test testUnknownEnumnull99 fun testUnknownEnum() { 100 expectPath("$.e") { Json.decodeFromString<EH>("""{"e": "foo"}""") } 101 } 102 103 @Serializable 104 @SerialName("f") 105 sealed class Sealed { 106 107 @Serializable 108 @SerialName("n") 109 class Nesting(val f: Sealed) : Sealed() 110 111 @Serializable 112 @SerialName("b") 113 class Box(val s: String) : Sealed() 114 115 @Serializable 116 @SerialName("d") 117 class DoubleNesting(val f: Sealed, val f2: Sealed) : Sealed() 118 } 119 120 // TODO use non-array polymorphism when https://github.com/Kotlin/kotlinx.serialization/issues/1839 is fixed 121 @Test <lambda>null122 fun testHugeNestingToCheckResize() = jvmOnly { 123 val json = Json { useArrayPolymorphism = true } 124 var outer = Sealed.Nesting(Sealed.Box("value")) 125 repeat(100) { 126 outer = Sealed.Nesting(outer) 127 } 128 val str = json.encodeToString(Sealed.serializer(), outer) 129 // throw-away data 130 json.decodeFromString(Sealed.serializer(), str) 131 132 val malformed = str.replace("\"value\"", "42") 133 val expectedPath = "$" + ".value.f".repeat(101) + ".value.s" 134 expectPath(expectedPath) { json.decodeFromString(Sealed.serializer(), malformed) } 135 } 136 137 @Test <lambda>null138 fun testDoubleNesting() = jvmOnly { 139 val json = Json { useArrayPolymorphism = true } 140 var outer1 = Sealed.Nesting(Sealed.Box("correct")) 141 repeat(64) { 142 outer1 = Sealed.Nesting(outer1) 143 } 144 145 var outer2 = Sealed.Nesting(Sealed.Box("incorrect")) 146 repeat(33) { 147 outer2 = Sealed.Nesting(outer2) 148 } 149 150 val str = json.encodeToString(Sealed.serializer(), Sealed.DoubleNesting(outer1, outer2)) 151 // throw-away data 152 json.decodeFromString(Sealed.serializer(), str) 153 154 val malformed = str.replace("\"incorrect\"", "42") 155 val expectedPath = "$.value.f2" + ".value.f".repeat(34) + ".value.s" 156 expectPath(expectedPath) { json.decodeFromString(Sealed.serializer(), malformed) } 157 } 158 expectPathnull159 private inline fun expectPath(path: String, block: () -> Unit) { 160 val message = runCatching { block() } 161 .exceptionOrNull()!!.message!! 162 assertContains(message, path) 163 } 164 } 165