summaryrefslogtreecommitdiff
path: root/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonMapKeysTest.kt
diff options
context:
space:
mode:
Diffstat (limited to 'formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonMapKeysTest.kt')
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonMapKeysTest.kt177
1 files changed, 177 insertions, 0 deletions
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonMapKeysTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonMapKeysTest.kt
new file mode 100644
index 00000000..560e51fe
--- /dev/null
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonMapKeysTest.kt
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2017-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.json
+
+import kotlinx.serialization.*
+import kotlinx.serialization.builtins.*
+import kotlinx.serialization.descriptors.PrimitiveKind
+import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
+import kotlinx.serialization.modules.SerializersModule
+import kotlinx.serialization.test.*
+import kotlin.jvm.*
+import kotlin.test.*
+
+@JvmInline
+@Serializable
+value class ComplexCarrier(val c: IntData)
+
+@JvmInline
+@Serializable
+value class PrimitiveCarrier(val c: String)
+
+data class ContextualValue(val c: String) {
+ companion object: KSerializer<ContextualValue> {
+ override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ContextualValue", PrimitiveKind.STRING)
+
+ override fun serialize(encoder: Encoder, value: ContextualValue) {
+ encoder.encodeString(value.c)
+ }
+
+ override fun deserialize(decoder: Decoder): ContextualValue {
+ return ContextualValue(decoder.decodeString())
+ }
+ }
+}
+
+class JsonMapKeysTest : JsonTestBase() {
+ @Serializable
+ private data class WithMap(val map: Map<Long, Long>)
+
+ @Serializable
+ private data class WithBooleanMap(val map: Map<Boolean, Boolean>)
+
+ @Serializable
+ private data class WithValueKeyMap(val map: Map<PrimitiveCarrier, Long>)
+
+ @Serializable
+ private data class WithEnum(val map: Map<SampleEnum, Long>)
+
+ @Serializable
+ private data class WithComplexKey(val map: Map<IntData, String>)
+
+ @Serializable
+ private data class WithComplexValueKey(val map: Map<ComplexCarrier, String>)
+
+ @Serializable
+ private data class WithContextualValueKey(val map: Map<@Contextual PrimitiveCarrier, Long>)
+
+ @Serializable
+ private data class WithContextualKey(val map: Map<@Contextual ContextualValue, Long>)
+
+ @Test
+ fun testMapKeysSupportNumbers() = parametrizedTest {
+ assertStringFormAndRestored(
+ """{"map":{"10":10,"20":20}}""",
+ WithMap(mapOf(10L to 10L, 20L to 20L)),
+ WithMap.serializer(),
+ default
+ )
+ }
+
+ @Test
+ fun testMapKeysSupportBooleans() = parametrizedTest {
+ assertStringFormAndRestored(
+ """{"map":{"true":false,"false":true}}""",
+ WithBooleanMap(mapOf(true to false, false to true)),
+ WithBooleanMap.serializer(),
+ default
+ )
+ }
+
+ // As a result of quoting ignorance when parsing primitives, it is possible to parse unquoted maps if Kotlin keys are non-string primitives.
+ // This is not spec-compliant, but I do not see any problems with it.
+ @Test
+ fun testMapDeserializesUnquotedKeys() = parametrizedTest {
+ assertEquals(WithMap(mapOf(10L to 10L, 20L to 20L)), default.decodeFromString("""{"map":{10:10,20:20}}"""))
+ assertEquals(
+ WithBooleanMap(mapOf(true to false, false to true)),
+ default.decodeFromString("""{"map":{true:false,false:true}}""")
+ )
+ assertFailsWithSerial("JsonDecodingException") {
+ default.decodeFromString(
+ MapSerializer(
+ String.serializer(),
+ Boolean.serializer()
+ ),"""{"map":{true:false,false:true}}"""
+ )
+ }
+ }
+
+ @Test
+ fun testStructuredMapKeysShouldBeProhibitedByDefault() = parametrizedTest { streaming ->
+ verifyProhibition(WithComplexKey(mapOf(IntData(42) to "42")), streaming)
+ verifyProhibition(WithComplexValueKey(mapOf(ComplexCarrier(IntData(42)) to "42")), streaming)
+ }
+
+ private inline fun <reified T: Any> verifyProhibition(value: T, streaming: JsonTestingMode) {
+ assertFailsWithSerialMessage("JsonEncodingException", "can't be used in JSON as a key in the map") {
+ Json.encodeToString(value, streaming)
+ }
+ }
+
+ @Test
+ fun testStructuredMapKeysAllowedWithFlag() = assertJsonFormAndRestored(
+ WithComplexKey.serializer(),
+ WithComplexKey(mapOf(IntData(42) to "42")),
+ """{"map":[{"intV":42},"42"]}""",
+ Json { allowStructuredMapKeys = true }
+ )
+
+ @Test
+ fun testStructuredValueMapKeysAllowedWithFlag() {
+ assertJsonFormAndRestored(
+ WithComplexValueKey.serializer(),
+ WithComplexValueKey(mapOf(ComplexCarrier(IntData(42)) to "42")),
+ """{"map":[{"intV":42},"42"]}""",
+ Json { allowStructuredMapKeys = true }
+ )
+ }
+
+ @Test
+ fun testEnumsAreAllowedAsMapKeys() = assertJsonFormAndRestored(
+ WithEnum.serializer(),
+ WithEnum(mapOf(SampleEnum.OptionA to 1L, SampleEnum.OptionC to 3L)),
+ """{"map":{"OptionA":1,"OptionC":3}}""",
+ Json
+ )
+
+ @Test
+ fun testPrimitivesAreAllowedAsValueMapKeys() {
+ assertJsonFormAndRestored(
+ WithValueKeyMap.serializer(),
+ WithValueKeyMap(mapOf(PrimitiveCarrier("fooKey") to 1)),
+ """{"map":{"fooKey":1}}""",
+ Json
+ )
+ }
+
+ @Test
+ fun testContextualValuePrimitivesAreAllowedAsValueMapKeys() {
+ assertJsonFormAndRestored(
+ WithContextualValueKey.serializer(),
+ WithContextualValueKey(mapOf(PrimitiveCarrier("fooKey") to 1)),
+ """{"map":{"fooKey":1}}""",
+ Json {
+ serializersModule =
+ SerializersModule { contextual(PrimitiveCarrier::class, PrimitiveCarrier.serializer()) }
+ }
+ )
+ }
+
+ @Test
+ fun testContextualPrimitivesAreAllowedAsValueMapKeys() {
+ assertJsonFormAndRestored(
+ WithContextualKey.serializer(),
+ WithContextualKey(mapOf(ContextualValue("fooKey") to 1)),
+ """{"map":{"fooKey":1}}""",
+ Json {
+ serializersModule = SerializersModule { contextual(ContextualValue::class, ContextualValue) }
+ }
+ )
+ }
+}