<lambda>null1 package kotlinx.serialization.features
2
3 import kotlinx.serialization.*
4 import kotlinx.serialization.builtins.*
5 import kotlinx.serialization.json.*
6 import kotlinx.serialization.test.*
7 import kotlin.test.*
8
9
10 class JsonNamingStrategyTest : JsonTestBase() {
11 @Serializable
12 data class Foo(
13 val simple: String = "a",
14 val oneWord: String = "b",
15 val already_in_snake: String = "c",
16 val aLotOfWords: String = "d",
17 val FirstCapitalized: String = "e",
18 val hasAcronymURL: Bar = Bar.BAZ,
19 val hasDigit123AndPostfix: Bar = Bar.QUX,
20 val coercionTest: Bar = Bar.QUX
21 )
22
23 enum class Bar { BAZ, QUX }
24
25 val jsonWithNaming = Json(default) {
26 namingStrategy = JsonNamingStrategy.SnakeCase
27 decodeEnumsCaseInsensitive = true // check that related feature does not break anything
28 }
29
30 @Test
31 fun testJsonNamingStrategyWithAlternativeNames() = doTest(Json(jsonWithNaming) {
32 useAlternativeNames = true
33 })
34
35 @Test
36 fun testJsonNamingStrategyWithoutAlternativeNames() = doTest(Json(jsonWithNaming) {
37 useAlternativeNames = false
38 })
39
40 private fun doTest(json: Json) {
41 val foo = Foo()
42 assertJsonFormAndRestored(
43 Foo.serializer(),
44 foo,
45 """{"simple":"a","one_word":"b","already_in_snake":"c","a_lot_of_words":"d","first_capitalized":"e","has_acronym_url":"BAZ","has_digit123_and_postfix":"QUX","coercion_test":"QUX"}""",
46 json
47 )
48 }
49
50 @Test
51 fun testNamingStrategyWorksWithCoercing() {
52 val j = Json(jsonWithNaming) {
53 coerceInputValues = true
54 useAlternativeNames = false
55 }
56 assertEquals(
57 Foo(),
58 j.decodeFromString("""{"simple":"a","one_word":"b","already_in_snake":"c","a_lot_of_words":"d","first_capitalized":"e","has_acronym_url":"baz","has_digit123_and_postfix":"qux","coercion_test":"invalid"}""")
59 )
60 }
61
62 @Test
63 fun testSnakeCaseStrategy() {
64 fun apply(name: String) =
65 JsonNamingStrategy.SnakeCase.serialNameForJson(String.serializer().descriptor, 0, name)
66
67 val cases = mapOf<String, String>(
68 "" to "",
69 "_" to "_",
70 "___" to "___",
71 "a" to "a",
72 "A" to "a",
73 "_1" to "_1",
74 "_a" to "_a",
75 "_A" to "_a",
76 "property" to "property",
77 "twoWords" to "two_words",
78 "threeDistinctWords" to "three_distinct_words",
79 "ThreeDistinctWords" to "three_distinct_words",
80 "Oneword" to "oneword",
81 "camel_Case_Underscores" to "camel_case_underscores",
82 "_many____underscores__" to "_many____underscores__",
83 "URLmapping" to "ur_lmapping",
84 "URLMapping" to "url_mapping",
85 "IOStream" to "io_stream",
86 "IOstream" to "i_ostream",
87 "myIo2Stream" to "my_io2_stream",
88 "myIO2Stream" to "my_io2_stream",
89 "myIO2stream" to "my_io2stream",
90 "myIO2streamMax" to "my_io2stream_max",
91 "InURLBetween" to "in_url_between",
92 "myHTTP2APIKey" to "my_http2_api_key",
93 "myHTTP2fastApiKey" to "my_http2fast_api_key",
94 "myHTTP23APIKey" to "my_http23_api_key",
95 "myHttp23ApiKey" to "my_http23_api_key",
96 "theWWW" to "the_www",
97 "theWWW_URL_xxx" to "the_www_url_xxx",
98 "hasDigit123AndPostfix" to "has_digit123_and_postfix"
99 )
100
101 cases.forEach { (input, expected) ->
102 assertEquals(expected, apply(input))
103 }
104 }
105
106 @Test
107 fun testKebabCaseStrategy() {
108 fun apply(name: String) =
109 JsonNamingStrategy.KebabCase.serialNameForJson(String.serializer().descriptor, 0, name)
110
111 val cases = mapOf<String, String>(
112 "" to "",
113 "_" to "_",
114 "-" to "-",
115 "___" to "___",
116 "---" to "---",
117 "a" to "a",
118 "A" to "a",
119 "-1" to "-1",
120 "-a" to "-a",
121 "-A" to "-a",
122 "property" to "property",
123 "twoWords" to "two-words",
124 "threeDistinctWords" to "three-distinct-words",
125 "ThreeDistinctWords" to "three-distinct-words",
126 "Oneword" to "oneword",
127 "camel-Case-WithDashes" to "camel-case-with-dashes",
128 "_many----dashes--" to "_many----dashes--",
129 "URLmapping" to "ur-lmapping",
130 "URLMapping" to "url-mapping",
131 "IOStream" to "io-stream",
132 "IOstream" to "i-ostream",
133 "myIo2Stream" to "my-io2-stream",
134 "myIO2Stream" to "my-io2-stream",
135 "myIO2stream" to "my-io2stream",
136 "myIO2streamMax" to "my-io2stream-max",
137 "InURLBetween" to "in-url-between",
138 "myHTTP2APIKey" to "my-http2-api-key",
139 "myHTTP2fastApiKey" to "my-http2fast-api-key",
140 "myHTTP23APIKey" to "my-http23-api-key",
141 "myHttp23ApiKey" to "my-http23-api-key",
142 "theWWW" to "the-www",
143 "theWWW-URL-xxx" to "the-www-url-xxx",
144 "hasDigit123AndPostfix" to "has-digit123-and-postfix"
145 )
146
147 cases.forEach { (input, expected) ->
148 assertEquals(expected, apply(input))
149 }
150 }
151
152 @Serializable
153 data class DontUseOriginal(val testCase: String)
154
155 @Test
156 fun testNamingStrategyOverridesOriginal() {
157 val json = Json(jsonWithNaming) {
158 ignoreUnknownKeys = true
159 }
160 parametrizedTest { mode ->
161 assertEquals(DontUseOriginal("a"), json.decodeFromString("""{"test_case":"a","testCase":"b"}""", mode))
162 }
163
164 val jsonThrows = Json(jsonWithNaming) {
165 ignoreUnknownKeys = false
166 }
167 parametrizedTest { mode ->
168 assertFailsWithMessage<SerializationException>("Encountered an unknown key 'testCase'") {
169 jsonThrows.decodeFromString<DontUseOriginal>("""{"test_case":"a","testCase":"b"}""", mode)
170 }
171 }
172 }
173
174 @Serializable
175 data class CollisionCheckPrimary(val testCase: String, val test_case: String)
176
177 @Serializable
178 data class CollisionCheckAlternate(val testCase: String, @JsonNames("test_case") val testCase2: String)
179
180 @Test
181 fun testNamingStrategyPrioritizesOverAlternative() {
182 val json = Json(jsonWithNaming) {
183 ignoreUnknownKeys = true
184 }
185 parametrizedTest { mode ->
186 assertFailsWithMessage<SerializationException>("The suggested name 'test_case' for property test_case is already one of the names for property testCase") {
187 json.decodeFromString<CollisionCheckPrimary>("""{"test_case":"a"}""", mode)
188 }
189 }
190 parametrizedTest { mode ->
191 assertFailsWithMessage<SerializationException>("The suggested name 'test_case' for property testCase2 is already one of the names for property testCase") {
192 json.decodeFromString<CollisionCheckAlternate>("""{"test_case":"a"}""", mode)
193 }
194 }
195 }
196
197
198 @Serializable
199 data class OriginalAsFallback(@JsonNames("testCase") val testCase: String)
200
201 @Test
202 fun testCanUseOriginalNameAsAlternative() {
203 val json = Json(jsonWithNaming) {
204 ignoreUnknownKeys = true
205 }
206 parametrizedTest { mode ->
207 assertEquals(OriginalAsFallback("b"), json.decodeFromString("""{"testCase":"b"}""", mode))
208 }
209 }
210
211 @Serializable
212 sealed interface SealedBase {
213 @Serializable
214 @JsonClassDiscriminator("typeSub")
215 sealed class SealedMid : SealedBase {
216 @Serializable
217 @SerialName("SealedSub1")
218 object SealedSub1 : SealedMid()
219 }
220
221 @Serializable
222 @SerialName("SealedSub2")
223 data class SealedSub2(val testCase: Int = 0) : SealedBase
224 }
225
226 @Serializable
227 data class Holder(val testBase: SealedBase, val testMid: SealedBase.SealedMid)
228
229 @Test
230 fun testNamingStrategyDoesNotAffectPolymorphism() {
231 val json = Json(jsonWithNaming) {
232 classDiscriminator = "typeBase"
233 }
234 val holder = Holder(SealedBase.SealedSub2(), SealedBase.SealedMid.SealedSub1)
235 assertJsonFormAndRestored(
236 Holder.serializer(),
237 holder,
238 """{"test_base":{"typeBase":"SealedSub2","test_case":0},"test_mid":{"typeSub":"SealedSub1"}}""",
239 json
240 )
241 }
242 }
243