<lambda>null1 package kotlinx.serialization.features
2
3 import kotlinx.serialization.*
4 import kotlinx.serialization.json.*
5 import kotlinx.serialization.test.*
6 import kotlin.test.*
7
8 @Suppress("EnumEntryName")
9 class JsonEnumsCaseInsensitiveTest: JsonTestBase() {
10 @Serializable
11 data class Foo(
12 val one: Bar = Bar.BAZ,
13 val two: Bar = Bar.QUX,
14 val three: Bar = Bar.QUX
15 )
16
17 enum class Bar { BAZ, QUX }
18
19 // It seems that we no longer report a warning that @Serializable is required for enums with @SerialName.
20 // It is still required for them to work at top-level.
21 @Serializable
22 enum class Cases {
23 ALL_CAPS,
24 MiXed,
25 all_lower,
26
27 @JsonNames("AltName")
28 hasAltNames,
29
30 @SerialName("SERIAL_NAME")
31 hasSerialName
32 }
33
34 @Serializable
35 data class EnumCases(val cases: List<Cases>)
36
37 val json = Json(default) { decodeEnumsCaseInsensitive = true }
38
39 @Test
40 fun testCases() = parametrizedTest { mode ->
41 val input =
42 """{"cases":["ALL_CAPS","all_caps","mixed","MIXED","miXed","all_lower","ALL_LOWER","all_Lower","hasAltNames","HASALTNAMES","altname","ALTNAME","AltName","SERIAL_NAME","serial_name"]}"""
43 val target = listOf(
44 Cases.ALL_CAPS,
45 Cases.ALL_CAPS,
46 Cases.MiXed,
47 Cases.MiXed,
48 Cases.MiXed,
49 Cases.all_lower,
50 Cases.all_lower,
51 Cases.all_lower,
52 Cases.hasAltNames,
53 Cases.hasAltNames,
54 Cases.hasAltNames,
55 Cases.hasAltNames,
56 Cases.hasAltNames,
57 Cases.hasSerialName,
58 Cases.hasSerialName
59 )
60 val decoded = json.decodeFromString<EnumCases>(input, mode)
61 assertEquals(EnumCases(target), decoded)
62 val encoded = json.encodeToString(decoded, mode)
63 assertEquals(
64 """{"cases":["ALL_CAPS","ALL_CAPS","MiXed","MiXed","MiXed","all_lower","all_lower","all_lower","hasAltNames","hasAltNames","hasAltNames","hasAltNames","hasAltNames","SERIAL_NAME","SERIAL_NAME"]}""",
65 encoded
66 )
67 }
68
69 @Test
70 fun testTopLevelList() = parametrizedTest { mode ->
71 val input = """["all_caps","serial_name"]"""
72 val decoded = json.decodeFromString<List<Cases>>(input, mode)
73 assertEquals(listOf(Cases.ALL_CAPS, Cases.hasSerialName), decoded)
74 assertEquals("""["ALL_CAPS","SERIAL_NAME"]""", json.encodeToString(decoded, mode))
75 }
76
77 @Test
78 fun testTopLevelEnum() = parametrizedTest { mode ->
79 val input = """"altName""""
80 val decoded = json.decodeFromString<Cases>(input, mode)
81 assertEquals(Cases.hasAltNames, decoded)
82 assertEquals(""""hasAltNames"""", json.encodeToString(decoded, mode))
83 }
84
85 @Test
86 fun testSimpleCase() = parametrizedTest { mode ->
87 val input = """{"one":"baz","two":"Qux","three":"QUX"}"""
88 val decoded = json.decodeFromString<Foo>(input, mode)
89 assertEquals(Foo(), decoded)
90 assertEquals("""{"one":"BAZ","two":"QUX","three":"QUX"}""", json.encodeToString(decoded, mode))
91 }
92
93 enum class E { VALUE_A, @JsonNames("ALTERNATIVE") VALUE_B }
94
95 @Test
96 fun testDocSample() {
97
98 val j = Json { decodeEnumsCaseInsensitive = true }
99 @Serializable
100 data class Outer(val enums: List<E>)
101
102 println(j.decodeFromString<Outer>("""{"enums":["value_A", "alternative"]}""").enums)
103 }
104
105 @Test
106 fun testCoercingStillWorks() = parametrizedTest { mode ->
107 val withCoercing = Json(json) { coerceInputValues = true }
108 val input = """{"one":"baz","two":"unknown","three":"Que"}"""
109 assertEquals(Foo(), withCoercing.decodeFromString<Foo>(input, mode))
110 }
111
112 @Test
113 fun testCaseInsensitivePriorityOverCoercing() = parametrizedTest { mode ->
114 val withCoercing = Json(json) { coerceInputValues = true }
115 val input = """{"one":"QuX","two":"Baz","three":"Que"}"""
116 assertEquals(Foo(Bar.QUX, Bar.BAZ, Bar.QUX), withCoercing.decodeFromString<Foo>(input, mode))
117 }
118
119 @Test
120 fun testCoercingStillWorksWithNulls() = parametrizedTest { mode ->
121 val withCoercing = Json(json) { coerceInputValues = true }
122 val input = """{"one":"baz","two":"null","three":null}"""
123 assertEquals(Foo(), withCoercing.decodeFromString<Foo>(input, mode))
124 }
125
126 @Test
127 fun testFeatureDisablesProperly() = parametrizedTest { mode ->
128 val disabled = Json(json) {
129 coerceInputValues = true
130 decodeEnumsCaseInsensitive = false
131 }
132 val input = """{"one":"BAZ","two":"BAz","three":"baz"}""" // two and three should be coerced to QUX
133 assertEquals(Foo(), disabled.decodeFromString<Foo>(input, mode))
134 }
135
136 @Test
137 fun testFeatureDisabledThrowsWithoutCoercing() = parametrizedTest { mode ->
138 val disabled = Json(json) {
139 coerceInputValues = false
140 decodeEnumsCaseInsensitive = false
141 }
142 val input = """{"one":"BAZ","two":"BAz","three":"baz"}"""
143 assertFailsWithMessage<SerializationException>("does not contain element with name 'BAz'") {
144 disabled.decodeFromString<Foo>(input, mode)
145 }
146 }
147
148 @Serializable enum class BadEnum { Bad, BAD }
149
150 @Serializable data class ListBadEnum(val l: List<BadEnum>)
151
152 @Test
153 fun testLowercaseClashThrowsException() = parametrizedTest { mode ->
154 assertFailsWithMessage<SerializationException>("""The suggested name 'bad' for enum value BAD is already one of the names for enum value Bad""") {
155 json.decodeFromString<Box<BadEnum>>("""{"boxed":"bad"}""", mode)
156 }
157 assertFailsWithMessage<SerializationException>("""The suggested name 'bad' for enum value BAD is already one of the names for enum value Bad""") {
158 json.decodeFromString<Box<BadEnum>>("""{"boxed":"unrelated"}""", mode)
159 }
160 }
161
162 @Test
163 fun testLowercaseClashHandledWithoutFeature() = parametrizedTest { mode ->
164 val disabled = Json(json) {
165 coerceInputValues = false
166 decodeEnumsCaseInsensitive = false
167 }
168 assertEquals(ListBadEnum(listOf(BadEnum.Bad, BadEnum.BAD)), disabled.decodeFromString("""{"l":["Bad","BAD"]}""", mode))
169 }
170 }
171