Skip to content

Commit fcb641a

Browse files
authored
Allow using value classes with primitive carriers as map keys without… (#1470)
Allow using value classes with primitive carriers as map keys without 'allowStructuredMapKeys' Fixes #1459
1 parent 9338a72 commit fcb641a

File tree

2 files changed

+48
-7
lines changed

2 files changed

+48
-7
lines changed

formats/json/commonMain/src/kotlinx/serialization/json/internal/WriteMode.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ internal inline fun <T, R1 : T, R2 : T> Json.selectMapMode(
3232
ifMap: () -> R1,
3333
ifList: () -> R2
3434
): T {
35-
val keyDescriptor = mapDescriptor.getElementDescriptor(0)
35+
val keyDescriptor = mapDescriptor.getElementDescriptor(0).carrierDescriptor
3636
val keyKind = keyDescriptor.kind
3737
return if (keyKind is PrimitiveKind || keyKind == SerialKind.ENUM) {
3838
ifMap()
@@ -42,3 +42,6 @@ internal inline fun <T, R1 : T, R2 : T> Json.selectMapMode(
4242
throw InvalidKeyKindException(keyDescriptor)
4343
}
4444
}
45+
46+
internal val SerialDescriptor.carrierDescriptor: SerialDescriptor
47+
get() = if (isInline) getElementDescriptor(0) else this

formats/json/commonTest/src/kotlinx/serialization/json/JsonMapKeysTest.kt

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,33 @@ package kotlinx.serialization.json
77
import kotlinx.serialization.*
88
import kotlinx.serialization.json.internal.*
99
import kotlinx.serialization.test.*
10+
import kotlin.jvm.*
1011
import kotlin.test.*
1112

13+
@JvmInline
14+
@Serializable
15+
value class ComplexCarrier(val c: IntData)
16+
17+
@JvmInline
18+
@Serializable
19+
value class PrimitiveCarrier(val c: String)
20+
1221
class JsonMapKeysTest : JsonTestBase() {
1322
@Serializable
1423
private data class WithMap(val map: Map<Long, Long>)
1524

25+
@Serializable
26+
private data class WithValueKeyMap(val map: Map<PrimitiveCarrier, Long>)
27+
1628
@Serializable
1729
private data class WithEnum(val map: Map<SampleEnum, Long>)
1830

1931
@Serializable
2032
private data class WithComplexKey(val map: Map<IntData, String>)
2133

34+
@Serializable
35+
private data class WithComplexValueKey(val map: Map<ComplexCarrier, String>)
36+
2237
@Test
2338
fun testMapKeysShouldBeStrings() = parametrizedTest(default) {
2439
assertStringFormAndRestored(
@@ -30,13 +45,16 @@ class JsonMapKeysTest : JsonTestBase() {
3045
}
3146

3247
@Test
33-
fun structuredMapKeysShouldBeBannedByDefault() = parametrizedTest { streaming ->
48+
fun testStructuredMapKeysShouldBeProhibitedByDefault() = parametrizedTest { streaming ->
49+
noLegacyJs {
50+
verifyProhibition(WithComplexKey(mapOf(IntData(42) to "42")), streaming)
51+
verifyProhibition(WithComplexValueKey(mapOf(ComplexCarrier(IntData(42)) to "42")), streaming)
52+
}
53+
}
54+
55+
private inline fun <reified T: Any> verifyProhibition(value: T, streaming: Boolean) {
3456
val e = assertFailsWith<JsonException> {
35-
Json.encodeToString(
36-
WithComplexKey.serializer(),
37-
WithComplexKey(mapOf(IntData(42) to "42")),
38-
streaming
39-
)
57+
Json.encodeToString(value, streaming)
4058
}
4159
assertTrue(e.message?.contains("can't be used in JSON as a key in the map") == true)
4260
}
@@ -49,11 +67,31 @@ class JsonMapKeysTest : JsonTestBase() {
4967
Json { allowStructuredMapKeys = true }
5068
)
5169

70+
@Test
71+
fun testStructuredValueMapKeysAllowedWithFlag() = noLegacyJs {
72+
assertJsonFormAndRestored(
73+
WithComplexValueKey.serializer(),
74+
WithComplexValueKey(mapOf(ComplexCarrier(IntData(42)) to "42")),
75+
"""{"map":[{"intV":42},"42"]}""",
76+
Json { allowStructuredMapKeys = true }
77+
)
78+
}
79+
5280
@Test
5381
fun testEnumsAreAllowedAsMapKeys() = assertJsonFormAndRestored(
5482
WithEnum.serializer(),
5583
WithEnum(mapOf(SampleEnum.OptionA to 1L, SampleEnum.OptionC to 3L)),
5684
"""{"map":{"OptionA":1,"OptionC":3}}""",
5785
Json
5886
)
87+
88+
@Test
89+
fun testPrimitivesAreAllowedAsValueMapKeys() = noLegacyJs {
90+
assertJsonFormAndRestored(
91+
WithValueKeyMap.serializer(),
92+
WithValueKeyMap(mapOf(PrimitiveCarrier("fooKey") to 1)),
93+
"""{"map":{"fooKey":1}}""",
94+
Json
95+
)
96+
}
5997
}

0 commit comments

Comments
 (0)