• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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