• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright 2017-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3  */
4 
5 package kotlinx.serialization.json
6 
7 import kotlinx.serialization.*
8 import kotlinx.serialization.builtins.*
9 import kotlinx.serialization.descriptors.PrimitiveKind
10 import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
11 import kotlinx.serialization.descriptors.SerialDescriptor
12 import kotlinx.serialization.encoding.Decoder
13 import kotlinx.serialization.encoding.Encoder
14 import kotlinx.serialization.modules.SerializersModule
15 import kotlinx.serialization.test.*
16 import kotlin.jvm.*
17 import kotlin.test.*
18 
19 @JvmInline
20 @Serializable
21 value class ComplexCarrier(val c: IntData)
22 
23 @JvmInline
24 @Serializable
25 value class PrimitiveCarrier(val c: String)
26 
27 data class ContextualValue(val c: String) {
28     companion object: KSerializer<ContextualValue> {
29         override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ContextualValue", PrimitiveKind.STRING)
30 
31         override fun serialize(encoder: Encoder, value: ContextualValue) {
32             encoder.encodeString(value.c)
33         }
34 
35         override fun deserialize(decoder: Decoder): ContextualValue {
36             return ContextualValue(decoder.decodeString())
37         }
38     }
39 }
40 
41 class JsonMapKeysTest : JsonTestBase() {
42     @Serializable
43     private data class WithMap(val map: Map<Long, Long>)
44 
45     @Serializable
46     private data class WithBooleanMap(val map: Map<Boolean, Boolean>)
47 
48     @Serializable
49     private data class WithValueKeyMap(val map: Map<PrimitiveCarrier, Long>)
50 
51     @Serializable
52     private data class WithEnum(val map: Map<SampleEnum, Long>)
53 
54     @Serializable
55     private data class WithComplexKey(val map: Map<IntData, String>)
56 
57     @Serializable
58     private data class WithComplexValueKey(val map: Map<ComplexCarrier, String>)
59 
60     @Serializable
61     private data class WithContextualValueKey(val map: Map<@Contextual PrimitiveCarrier, Long>)
62 
63     @Serializable
64     private data class WithContextualKey(val map: Map<@Contextual ContextualValue, Long>)
65 
66     @Test
testMapKeysSupportNumbersnull67     fun testMapKeysSupportNumbers() = parametrizedTest {
68         assertStringFormAndRestored(
69             """{"map":{"10":10,"20":20}}""",
70             WithMap(mapOf(10L to 10L, 20L to 20L)),
71             WithMap.serializer(),
72             default
73         )
74     }
75 
76     @Test
<lambda>null77     fun testMapKeysSupportBooleans() = parametrizedTest {
78         assertStringFormAndRestored(
79             """{"map":{"true":false,"false":true}}""",
80             WithBooleanMap(mapOf(true to false, false to true)),
81             WithBooleanMap.serializer(),
82             default
83         )
84     }
85 
86     // As a result of quoting ignorance when parsing primitives, it is possible to parse unquoted maps if Kotlin keys are non-string primitives.
87     // This is not spec-compliant, but I do not see any problems with it.
88     @Test
testMapDeserializesUnquotedKeysnull89     fun testMapDeserializesUnquotedKeys() = parametrizedTest {
90         assertEquals(WithMap(mapOf(10L to 10L, 20L to 20L)), default.decodeFromString("""{"map":{10:10,20:20}}"""))
91         assertEquals(
92             WithBooleanMap(mapOf(true to false, false to true)),
93             default.decodeFromString("""{"map":{true:false,false:true}}""")
94         )
95         assertFailsWithSerial("JsonDecodingException") {
96             default.decodeFromString(
97                 MapSerializer(
98                     String.serializer(),
99                     Boolean.serializer()
100                 ),"""{"map":{true:false,false:true}}"""
101             )
102         }
103     }
104 
105     @Test
streamingnull106     fun testStructuredMapKeysShouldBeProhibitedByDefault() = parametrizedTest { streaming ->
107         verifyProhibition(WithComplexKey(mapOf(IntData(42) to "42")), streaming)
108         verifyProhibition(WithComplexValueKey(mapOf(ComplexCarrier(IntData(42)) to "42")), streaming)
109     }
110 
verifyProhibitionnull111     private inline fun <reified T: Any> verifyProhibition(value: T, streaming: JsonTestingMode) {
112         assertFailsWithSerialMessage("JsonEncodingException", "can't be used in JSON as a key in the map") {
113             Json.encodeToString(value, streaming)
114         }
115     }
116 
117     @Test
testStructuredMapKeysAllowedWithFlagnull118     fun testStructuredMapKeysAllowedWithFlag() = assertJsonFormAndRestored(
119         WithComplexKey.serializer(),
120         WithComplexKey(mapOf(IntData(42) to "42")),
121         """{"map":[{"intV":42},"42"]}""",
122         Json { allowStructuredMapKeys = true }
123     )
124 
125     @Test
testStructuredValueMapKeysAllowedWithFlagnull126     fun testStructuredValueMapKeysAllowedWithFlag() {
127         assertJsonFormAndRestored(
128             WithComplexValueKey.serializer(),
129             WithComplexValueKey(mapOf(ComplexCarrier(IntData(42)) to "42")),
130             """{"map":[{"intV":42},"42"]}""",
131             Json { allowStructuredMapKeys = true }
132         )
133     }
134 
135     @Test
testEnumsAreAllowedAsMapKeysnull136     fun testEnumsAreAllowedAsMapKeys() = assertJsonFormAndRestored(
137         WithEnum.serializer(),
138         WithEnum(mapOf(SampleEnum.OptionA to 1L, SampleEnum.OptionC to 3L)),
139         """{"map":{"OptionA":1,"OptionC":3}}""",
140         Json
141     )
142 
143     @Test
144     fun testPrimitivesAreAllowedAsValueMapKeys() {
145         assertJsonFormAndRestored(
146             WithValueKeyMap.serializer(),
147             WithValueKeyMap(mapOf(PrimitiveCarrier("fooKey") to 1)),
148             """{"map":{"fooKey":1}}""",
149             Json
150         )
151     }
152 
153     @Test
testContextualValuePrimitivesAreAllowedAsValueMapKeysnull154     fun testContextualValuePrimitivesAreAllowedAsValueMapKeys() {
155         assertJsonFormAndRestored(
156             WithContextualValueKey.serializer(),
157             WithContextualValueKey(mapOf(PrimitiveCarrier("fooKey") to 1)),
158             """{"map":{"fooKey":1}}""",
159             Json {
160                 serializersModule =
161                     SerializersModule { contextual(PrimitiveCarrier::class, PrimitiveCarrier.serializer()) }
162             }
163         )
164     }
165 
166     @Test
testContextualPrimitivesAreAllowedAsValueMapKeysnull167     fun testContextualPrimitivesAreAllowedAsValueMapKeys() {
168         assertJsonFormAndRestored(
169             WithContextualKey.serializer(),
170             WithContextualKey(mapOf(ContextualValue("fooKey") to 1)),
171             """{"map":{"fooKey":1}}""",
172             Json {
173                 serializersModule = SerializersModule { contextual(ContextualValue::class, ContextualValue) }
174             }
175         )
176     }
177 }
178