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