1 /* <lambda>null2 * Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. 3 */ 4 5 package kotlinx.serialization.features 6 7 import kotlinx.serialization.* 8 import kotlinx.serialization.json.* 9 import kotlin.test.* 10 11 class JsonCommentsTest: JsonTestBase() { 12 val json = Json(default) { 13 allowComments = true 14 allowTrailingComma = true 15 } 16 17 val withLenient = Json(json) { 18 isLenient = true 19 ignoreUnknownKeys = true 20 } 21 22 @Test 23 fun testBasic() = parametrizedTest { mode -> 24 val inputBlock = """{"data": "b" /*value b*/ }""" 25 val inputLine = "{\"data\": \"b\" // value b \n }" 26 assertEquals(StringData("b"), json.decodeFromString(inputBlock, mode)) 27 assertEquals(StringData("b"), json.decodeFromString(inputLine, mode)) 28 } 29 30 @Serializable 31 data class Target(val key: String, val key2: List<Int>, val key3: NestedTarget, val key4: String) 32 33 @Serializable 34 data class NestedTarget(val nestedKey: String) 35 36 private fun target(key4: String): Target = Target("value", listOf(1, 2), NestedTarget("foo"), key4) 37 38 @Test 39 fun testAllBlocks() = parametrizedTest { mode -> 40 val input = """{ /*beginning*/ 41 /*before key*/ "key" /*after key*/ : /*after colon*/ "value" /*before comma*/, 42 "key2": [ /*array1*/ 1, /*array2*/ 2, /*end array*/], 43 "key3": { /*nested obj*/ "nestedKey": "foo"} /*after nested*/, 44 "key4": "/*comment inside quotes is a part of value*/", 45 /*before end*/ 46 }""" 47 assertEquals(target("/*comment inside quotes is a part of value*/"), json.decodeFromString(input, mode)) 48 } 49 50 @Test 51 fun testAllLines() = parametrizedTest { mode -> 52 val input = """{ //beginning 53 //before key 54 "key" // after key 55 : // after colon 56 "value" //before comma 57 , 58 "key2": [ //array1 59 1, //array2 60 2, //end array 61 ], 62 "key3": { //nested obj 63 "nestedKey": "foo" 64 } , //after nested 65 "key4": "//comment inside quotes is a part of value", 66 //before end 67 }""" 68 assertEquals(target("//comment inside quotes is a part of value"), json.decodeFromString(input, mode)) 69 } 70 71 @Test 72 fun testMixed() = parametrizedTest { mode -> 73 val input = """{ // begin 74 "key": "value", // after 75 "key2": /* array */ /*another comment */ [1, 2], 76 "key3": /* //this is a block comment */ { "nestedKey": // /*this is a line comment*/ "bar" 77 "foo" }, 78 "key4": /* nesting block comments /* not supported */ "*/" 79 /* end */}""" 80 assertEquals(target("*/"), json.decodeFromString(input, mode)) 81 } 82 83 @Test 84 fun testWeirdKeys() { 85 val map = mapOf( 86 "// comment inside quotes is a part of key" to "/* comment inside quotes is a part of value */", 87 "/*key */" to "/* value", 88 "/* key" to "*/ value" 89 ) 90 val input = """/* before begin */ 91 { 92 ${map.entries.joinToString(separator = ",\n") { (k, v) -> "\"$k\" : \"$v\"" }} 93 } // after end 94 """.trimIndent() 95 val afterMap = json.parseToJsonElement(input).jsonObject.mapValues { (_, v) -> 96 v as JsonPrimitive 97 assertTrue(v.isString) 98 v.content 99 } 100 assertEquals(map, afterMap) 101 } 102 103 @Test 104 fun testWithLenient() = parametrizedTest { mode -> 105 val input = """{ //beginning 106 //before key 107 key // after key 108 : // after colon 109 value //before comma 110 , 111 key2: [ //array1 112 1, //array2 113 2, //end array 114 ], 115 key3: { //nested obj 116 nestedKey: "foo" 117 } , //after nested 118 key4: value//comment_cannot_break_value_apart, 119 key5: //comment without quotes where new token expected is still a comment 120 value5, 121 //before end 122 }""" 123 assertEquals(target("value//comment_cannot_break_value_apart"), withLenient.decodeFromString(input, mode)) 124 } 125 126 @Test 127 fun testUnclosedCommentsErrorMsg() = parametrizedTest { mode -> 128 val input = """{"data": "x"} // no newline""" 129 assertEquals(StringData("x"), json.decodeFromString<StringData>(input, mode)) 130 val input2 = """{"data": "x"} /* no endblock""" 131 assertFailsWith<SerializationException>("Expected end of the block comment: \"*/\", but had EOF instead at path: \$") { 132 json.decodeFromString<StringData>(input2, mode) 133 } 134 } 135 136 private val lexerBatchSize = 16 * 1024 137 138 @Test 139 fun testVeryLargeComments() = parametrizedTest { mode -> 140 val strLen = lexerBatchSize * 2 + 42 141 val inputLine = """{"data": //a""" + "a".repeat(strLen) + "\n\"x\"}" 142 assertEquals(StringData("x"), json.decodeFromString<StringData>(inputLine, mode)) 143 val inputBlock = """{"data": /*a""" + "a".repeat(strLen) + "*/\"x\"}" 144 assertEquals(StringData("x"), json.decodeFromString<StringData>(inputBlock, mode)) 145 } 146 147 @Test 148 fun testCommentsOnThresholdEdge() = parametrizedTest { mode -> 149 val inputPrefix = """{"data": /*a""" 150 // Here, we test the situation when closing */ is divided in buffer: 151 // * fits in the initial buffer, but / is not. 152 // E.g. situation with batches looks like this: ['{', '"', 'd', ..., '*'], ['/', ...] 153 val bloatSize = lexerBatchSize - inputPrefix.length - 1 154 val inputLine = inputPrefix + "a".repeat(bloatSize) + "*/\"x\"}" 155 assertEquals(StringData("x"), json.decodeFromString<StringData>(inputLine, mode)) 156 157 // Test when * is unclosed and last in buffer: 158 val inputLine2 = inputPrefix + "a".repeat(bloatSize) + "*" 159 assertFailsWith<SerializationException>("Expected end of the block comment: \"*/\", but had EOF instead at path: \$") { 160 json.decodeFromString<StringData>(inputLine2, mode) 161 } 162 163 } 164 165 } 166