summaryrefslogtreecommitdiff
path: root/formats
diff options
context:
space:
mode:
authorLeonid Startsev <sandwwraith@users.noreply.github.com>2023-07-05 19:18:17 +0200
committerGitHub <noreply@github.com>2023-07-05 19:18:17 +0200
commit782b9f3be9970e0fd36215a86bf7fdba9f2bfe83 (patch)
treedcec941bc829929cd66d004825c5eb728e5d8d23 /formats
parenta87b0f1d89f927896bbeeefa4377bfdeafbf7cb0 (diff)
downloadkotlinx.serialization-782b9f3be9970e0fd36215a86bf7fdba9f2bfe83.tar.gz
Introduce 'decodeEnumsCaseInsensitive' feature to Json. (#2345)
It allows decoding enum values in a case-insensitive manner. It does not affect CLASS kinds or encoding. It is one of the most-voted feature requests. Also enhance JsonNamingStrategy documentation. Fixes #209
Diffstat (limited to 'formats')
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/JsonEnumsCaseInsensitiveTest.kt171
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/JsonNamingStrategyTest.kt1
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/test/TestingFramework.kt4
-rw-r--r--formats/json/api/kotlinx-serialization-json.api3
-rw-r--r--formats/json/commonMain/src/kotlinx/serialization/json/Json.kt33
-rw-r--r--formats/json/commonMain/src/kotlinx/serialization/json/JsonConfiguration.kt6
-rw-r--r--formats/json/commonMain/src/kotlinx/serialization/json/JsonNamingStrategy.kt13
-rw-r--r--formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonNamesMap.kt35
8 files changed, 244 insertions, 22 deletions
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/features/JsonEnumsCaseInsensitiveTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/JsonEnumsCaseInsensitiveTest.kt
new file mode 100644
index 00000000..ce80a346
--- /dev/null
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/features/JsonEnumsCaseInsensitiveTest.kt
@@ -0,0 +1,171 @@
+package kotlinx.serialization.features
+
+import kotlinx.serialization.*
+import kotlinx.serialization.json.*
+import kotlinx.serialization.test.*
+import kotlin.test.*
+
+@Suppress("EnumEntryName")
+class JsonEnumsCaseInsensitiveTest: JsonTestBase() {
+ @Serializable
+ data class Foo(
+ val one: Bar = Bar.BAZ,
+ val two: Bar = Bar.QUX,
+ val three: Bar = Bar.QUX
+ )
+
+ enum class Bar { BAZ, QUX }
+
+ // It seems that we no longer report a warning that @Serializable is required for enums with @SerialName.
+ // It is still required for them to work at top-level.
+ @Serializable
+ enum class Cases {
+ ALL_CAPS,
+ MiXed,
+ all_lower,
+
+ @JsonNames("AltName")
+ hasAltNames,
+
+ @SerialName("SERIAL_NAME")
+ hasSerialName
+ }
+
+ @Serializable
+ data class EnumCases(val cases: List<Cases>)
+
+ val json = Json(default) { decodeEnumsCaseInsensitive = true }
+
+ @Test
+ fun testCases() = noLegacyJs { parametrizedTest { mode ->
+ val input =
+ """{"cases":["ALL_CAPS","all_caps","mixed","MIXED","miXed","all_lower","ALL_LOWER","all_Lower","hasAltNames","HASALTNAMES","altname","ALTNAME","AltName","SERIAL_NAME","serial_name"]}"""
+ val target = listOf(
+ Cases.ALL_CAPS,
+ Cases.ALL_CAPS,
+ Cases.MiXed,
+ Cases.MiXed,
+ Cases.MiXed,
+ Cases.all_lower,
+ Cases.all_lower,
+ Cases.all_lower,
+ Cases.hasAltNames,
+ Cases.hasAltNames,
+ Cases.hasAltNames,
+ Cases.hasAltNames,
+ Cases.hasAltNames,
+ Cases.hasSerialName,
+ Cases.hasSerialName
+ )
+ val decoded = json.decodeFromString<EnumCases>(input, mode)
+ assertEquals(EnumCases(target), decoded)
+ val encoded = json.encodeToString(decoded, mode)
+ assertEquals(
+ """{"cases":["ALL_CAPS","ALL_CAPS","MiXed","MiXed","MiXed","all_lower","all_lower","all_lower","hasAltNames","hasAltNames","hasAltNames","hasAltNames","hasAltNames","SERIAL_NAME","SERIAL_NAME"]}""",
+ encoded
+ )
+ }}
+
+ @Test
+ fun testTopLevelList() = noLegacyJs { parametrizedTest { mode ->
+ val input = """["all_caps","serial_name"]"""
+ val decoded = json.decodeFromString<List<Cases>>(input, mode)
+ assertEquals(listOf(Cases.ALL_CAPS, Cases.hasSerialName), decoded)
+ assertEquals("""["ALL_CAPS","SERIAL_NAME"]""", json.encodeToString(decoded, mode))
+ }}
+
+ @Test
+ fun testTopLevelEnum() = noLegacyJs { parametrizedTest { mode ->
+ val input = """"altName""""
+ val decoded = json.decodeFromString<Cases>(input, mode)
+ assertEquals(Cases.hasAltNames, decoded)
+ assertEquals(""""hasAltNames"""", json.encodeToString(decoded, mode))
+ }}
+
+ @Test
+ fun testSimpleCase() = parametrizedTest { mode ->
+ val input = """{"one":"baz","two":"Qux","three":"QUX"}"""
+ val decoded = json.decodeFromString<Foo>(input, mode)
+ assertEquals(Foo(), decoded)
+ assertEquals("""{"one":"BAZ","two":"QUX","three":"QUX"}""", json.encodeToString(decoded, mode))
+ }
+
+ enum class E { VALUE_A, @JsonNames("ALTERNATIVE") VALUE_B }
+
+ @Test
+ fun testDocSample() = noLegacyJs {
+
+ val j = Json { decodeEnumsCaseInsensitive = true }
+ @Serializable
+ data class Outer(val enums: List<E>)
+
+ println(j.decodeFromString<Outer>("""{"enums":["value_A", "alternative"]}""").enums)
+ }
+
+ @Test
+ fun testCoercingStillWorks() = parametrizedTest { mode ->
+ val withCoercing = Json(json) { coerceInputValues = true }
+ val input = """{"one":"baz","two":"unknown","three":"Que"}"""
+ assertEquals(Foo(), withCoercing.decodeFromString<Foo>(input, mode))
+ }
+
+ @Test
+ fun testCaseInsensitivePriorityOverCoercing() = parametrizedTest { mode ->
+ val withCoercing = Json(json) { coerceInputValues = true }
+ val input = """{"one":"QuX","two":"Baz","three":"Que"}"""
+ assertEquals(Foo(Bar.QUX, Bar.BAZ, Bar.QUX), withCoercing.decodeFromString<Foo>(input, mode))
+ }
+
+ @Test
+ fun testCoercingStillWorksWithNulls() = parametrizedTest { mode ->
+ val withCoercing = Json(json) { coerceInputValues = true }
+ val input = """{"one":"baz","two":"null","three":null}"""
+ assertEquals(Foo(), withCoercing.decodeFromString<Foo>(input, mode))
+ }
+
+ @Test
+ fun testFeatureDisablesProperly() = parametrizedTest { mode ->
+ val disabled = Json(json) {
+ coerceInputValues = true
+ decodeEnumsCaseInsensitive = false
+ }
+ val input = """{"one":"BAZ","two":"BAz","three":"baz"}""" // two and three should be coerced to QUX
+ assertEquals(Foo(), disabled.decodeFromString<Foo>(input, mode))
+ }
+
+ @Test
+ fun testFeatureDisabledThrowsWithoutCoercing() = parametrizedTest { mode ->
+ val disabled = Json(json) {
+ coerceInputValues = false
+ decodeEnumsCaseInsensitive = false
+ }
+ val input = """{"one":"BAZ","two":"BAz","three":"baz"}"""
+ assertFailsWithMessage<SerializationException>("does not contain element with name 'BAz'") {
+ disabled.decodeFromString<Foo>(input, mode)
+ }
+ }
+
+ @Serializable enum class BadEnum { Bad, BAD }
+
+ @Serializable data class ListBadEnum(val l: List<BadEnum>)
+
+ @Test
+ fun testLowercaseClashThrowsException() = parametrizedTest { mode ->
+ assertFailsWithMessage<SerializationException>("""The suggested name 'bad' for enum value BAD is already one of the names for enum value Bad""") {
+ // an explicit serializer is required for JSLegacy
+ json.decodeFromString(Box.serializer(BadEnum.serializer()),"""{"boxed":"bad"}""", mode)
+ }
+ assertFailsWithMessage<SerializationException>("""The suggested name 'bad' for enum value BAD is already one of the names for enum value Bad""") {
+ json.decodeFromString(Box.serializer(BadEnum.serializer()),"""{"boxed":"unrelated"}""", mode)
+ }
+ }
+
+ @Test
+ fun testLowercaseClashHandledWithoutFeature() = parametrizedTest { mode ->
+ val disabled = Json(json) {
+ coerceInputValues = false
+ decodeEnumsCaseInsensitive = false
+ }
+ assertEquals(ListBadEnum(listOf(BadEnum.Bad, BadEnum.BAD)), disabled.decodeFromString("""{"l":["Bad","BAD"]}"""))
+ }
+}
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/features/JsonNamingStrategyTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/JsonNamingStrategyTest.kt
index 330d5d2b..68f36def 100644
--- a/formats/json-tests/commonTest/src/kotlinx/serialization/features/JsonNamingStrategyTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/features/JsonNamingStrategyTest.kt
@@ -24,6 +24,7 @@ class JsonNamingStrategyTest : JsonTestBase() {
val jsonWithNaming = Json(default) {
namingStrategy = JsonNamingStrategy.SnakeCase
+ decodeEnumsCaseInsensitive = true // check that related feature does not break anything
}
@Test
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/test/TestingFramework.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/test/TestingFramework.kt
index b46afe69..e941f047 100644
--- a/formats/json-tests/commonTest/src/kotlinx/serialization/test/TestingFramework.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/test/TestingFramework.kt
@@ -78,7 +78,7 @@ inline fun assertFailsWithSerialMessage(
)
assertTrue(
exception.message!!.contains(message),
- "expected:<${exception.message}> but was:<$message>"
+ "expected:<$message> but was:<${exception.message}>"
)
}
inline fun <reified T : Throwable> assertFailsWithMessage(
@@ -89,6 +89,6 @@ inline fun <reified T : Throwable> assertFailsWithMessage(
val exception = assertFailsWith(T::class, assertionMessage, block)
assertTrue(
exception.message!!.contains(message),
- "expected:<${exception.message}> but was:<$message>"
+ "expected:<$message> but was:<${exception.message}>"
)
}
diff --git a/formats/json/api/kotlinx-serialization-json.api b/formats/json/api/kotlinx-serialization-json.api
index 663bd997..ec79e13d 100644
--- a/formats/json/api/kotlinx-serialization-json.api
+++ b/formats/json/api/kotlinx-serialization-json.api
@@ -88,6 +88,7 @@ public final class kotlinx/serialization/json/JsonBuilder {
public final fun getAllowStructuredMapKeys ()Z
public final fun getClassDiscriminator ()Ljava/lang/String;
public final fun getCoerceInputValues ()Z
+ public final fun getDecodeEnumsCaseInsensitive ()Z
public final fun getEncodeDefaults ()Z
public final fun getExplicitNulls ()Z
public final fun getIgnoreUnknownKeys ()Z
@@ -102,6 +103,7 @@ public final class kotlinx/serialization/json/JsonBuilder {
public final fun setAllowStructuredMapKeys (Z)V
public final fun setClassDiscriminator (Ljava/lang/String;)V
public final fun setCoerceInputValues (Z)V
+ public final fun setDecodeEnumsCaseInsensitive (Z)V
public final fun setEncodeDefaults (Z)V
public final fun setExplicitNulls (Z)V
public final fun setIgnoreUnknownKeys (Z)V
@@ -129,6 +131,7 @@ public final class kotlinx/serialization/json/JsonConfiguration {
public final fun getAllowStructuredMapKeys ()Z
public final fun getClassDiscriminator ()Ljava/lang/String;
public final fun getCoerceInputValues ()Z
+ public final fun getDecodeEnumsCaseInsensitive ()Z
public final fun getEncodeDefaults ()Z
public final fun getExplicitNulls ()Z
public final fun getIgnoreUnknownKeys ()Z
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/Json.kt b/formats/json/commonMain/src/kotlinx/serialization/json/Json.kt
index 443f1dc3..40dcc23e 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/Json.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/Json.kt
@@ -288,7 +288,7 @@ public class JsonBuilder internal constructor(json: Json) {
/**
* Enables coercing incorrect JSON values to the default property value in the following cases:
- * 1. JSON value is `null` but property type is non-nullable.
+ * 1. JSON value is `null` but the property type is non-nullable.
* 2. Property type is an enum type, but JSON value contains unknown enum member.
*
* `false` by default.
@@ -336,6 +336,35 @@ public class JsonBuilder internal constructor(json: Json) {
public var namingStrategy: JsonNamingStrategy? = json.configuration.namingStrategy
/**
+ * Enables decoding enum values in a case-insensitive manner.
+ * Encoding is not affected.
+ *
+ * This affects both enum serial names and alternative names (specified with the [JsonNames] annotation).
+ * In the following example, string `[VALUE_A, VALUE_B]` will be printed:
+ * ```
+ * enum class E { VALUE_A, @JsonNames("ALTERNATIVE") VALUE_B }
+ *
+ * @Serializable
+ * data class Outer(val enums: List<E>)
+ *
+ * val j = Json { decodeEnumsCaseInsensitive = true }
+ * println(j.decodeFromString<Outer>("""{"enums":["value_A", "alternative"]}""").enums)
+ * ```
+ *
+ * If this feature is enabled,
+ * it is no longer possible to decode enum values that have the same name in a lowercase form.
+ * The following code will throw a serialization exception:
+ *
+ * ```
+ * enum class BadEnum { Bad, BAD }
+ * val j = Json { decodeEnumsCaseInsensitive = true }
+ * j.decodeFromString<Box<BadEnum>>("""{"boxed":"bad"}""")
+ * ```
+ */
+ @ExperimentalSerializationApi
+ public var decodeEnumsCaseInsensitive: Boolean = json.configuration.decodeEnumsCaseInsensitive
+
+ /**
* Module with contextual and polymorphic serializers to be used in the resulting [Json] instance.
*
* @see SerializersModule
@@ -367,7 +396,7 @@ public class JsonBuilder internal constructor(json: Json) {
allowStructuredMapKeys, prettyPrint, explicitNulls, prettyPrintIndent,
coerceInputValues, useArrayPolymorphism,
classDiscriminator, allowSpecialFloatingPointValues, useAlternativeNames,
- namingStrategy
+ namingStrategy, decodeEnumsCaseInsensitive
)
}
}
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/JsonConfiguration.kt b/formats/json/commonMain/src/kotlinx/serialization/json/JsonConfiguration.kt
index d17d0fcc..ea653a64 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/JsonConfiguration.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/JsonConfiguration.kt
@@ -9,7 +9,7 @@ import kotlinx.serialization.*
* Can be used for debug purposes and for custom Json-specific serializers
* via [JsonEncoder] and [JsonDecoder].
*
- * Standalone configuration object is meaningless and can nor be used outside of the
+ * Standalone configuration object is meaningless and can nor be used outside the
* [Json], neither new [Json] instance can be created from it.
*
* Detailed description of each property is available in [JsonBuilder] class.
@@ -31,6 +31,8 @@ public class JsonConfiguration @OptIn(ExperimentalSerializationApi::class) inter
public val useAlternativeNames: Boolean = true,
@ExperimentalSerializationApi
public val namingStrategy: JsonNamingStrategy? = null,
+ @ExperimentalSerializationApi
+ public val decodeEnumsCaseInsensitive: Boolean = false
) {
/** @suppress Dokka **/
@@ -40,6 +42,6 @@ public class JsonConfiguration @OptIn(ExperimentalSerializationApi::class) inter
"allowStructuredMapKeys=$allowStructuredMapKeys, prettyPrint=$prettyPrint, explicitNulls=$explicitNulls, " +
"prettyPrintIndent='$prettyPrintIndent', coerceInputValues=$coerceInputValues, useArrayPolymorphism=$useArrayPolymorphism, " +
"classDiscriminator='$classDiscriminator', allowSpecialFloatingPointValues=$allowSpecialFloatingPointValues, useAlternativeNames=$useAlternativeNames, " +
- "namingStrategy=$namingStrategy)"
+ "namingStrategy=$namingStrategy, decodeEnumsCaseInsensitive=$decodeEnumsCaseInsensitive)"
}
}
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/JsonNamingStrategy.kt b/formats/json/commonMain/src/kotlinx/serialization/json/JsonNamingStrategy.kt
index 060572af..64b4e0b7 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/JsonNamingStrategy.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/JsonNamingStrategy.kt
@@ -7,10 +7,11 @@ import kotlinx.serialization.descriptors.*
/**
* Represents naming strategy — a transformer for serial names in a [Json] format.
* Transformed serial names are used for both serialization and deserialization.
- * Actual transformation happens in the [serialNameForJson] function.
* A naming strategy is always applied globally in the Json configuration builder
* (see [JsonBuilder.namingStrategy]).
- * However, it is possible to apply additional filtering inside the transformer using the `descriptor` parameter in [serialNameForJson].
+ *
+ * Actual transformation happens in the [serialNameForJson] function.
+ * It is possible to apply additional filtering inside the transformer using the `descriptor` parameter in [serialNameForJson].
*
* Original serial names are never used after transformation, so they are ignored in a Json input.
* If the original serial name is present in the Json input but transformed is not,
@@ -21,7 +22,7 @@ import kotlinx.serialization.descriptors.*
*
* * Due to the nature of kotlinx.serialization framework, naming strategy transformation is applied to all properties regardless
* of whether their serial name was taken from the property name or provided by @[SerialName] annotation.
- * Effectively it means one cannot avoid transformation by explicitly specifying the serial name.
+ * Effectively, it means one cannot avoid transformation by explicitly specifying the serial name.
*
* * Collision of the transformed name with any other (transformed) properties serial names or any alternative names
* specified with [JsonNames] will lead to a deserialization exception.
@@ -40,7 +41,7 @@ import kotlinx.serialization.descriptors.*
* changing one without the other may introduce bugs in many unexpected ways.
* The lack of a single place of definition, the inability to use automated tools, and more error-prone code lead
* to greater maintenance efforts for code with global naming strategies.
- * However, there are cases where usage of naming strategies is inevitable, such as interop with existing API or migrating a large codebase.
+ * However, there are cases where usage of naming strategies is inevitable, such as interop with an existing API or migrating a large codebase.
* Therefore, one should carefully weigh the pros and cons before considering adding global naming strategies to an application.
*/
@ExperimentalSerializationApi
@@ -56,7 +57,7 @@ public fun interface JsonNamingStrategy {
* annotations (see [SerialDescriptor.getElementAnnotations]) or element optionality (see [SerialDescriptor.isElementOptional]).
*
* Note that invocations of this function are cached for performance reasons.
- * Caching strategy is an implementation detail and shouldn't be assumed as a part of the public API contract, as it may be changed in future releases.
+ * Caching strategy is an implementation detail and should not be assumed as a part of the public API contract, as it may be changed in future releases.
* Therefore, it is essential for this function to be pure: it should not have any side effects, and it should
* return the same String for a given [descriptor], [elementIndex], and [serialName], regardless of the number of invocations.
*/
@@ -74,7 +75,7 @@ public fun interface JsonNamingStrategy {
*
* **Transformation rules**
*
- * Words bounds are defined by uppercase characters. If there is a single uppercase char, it is transformed into lowercase one with underscore in front:
+ * Words' bounds are defined by uppercase characters. If there is a single uppercase char, it is transformed into lowercase one with underscore in front:
* `twoWords` -> `two_words`. No underscore is added if it was a beginning of the name: `MyProperty` -> `my_property`. Also, no underscore is added if it was already there:
* `camel_Case_Underscores` -> `camel_case_underscores`.
*
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonNamesMap.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonNamesMap.kt
index fc9cc19b..8acd8fc4 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonNamesMap.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonNamesMap.kt
@@ -17,9 +17,10 @@ internal val JsonSerializationNamesKey = DescriptorSchemaCache.Key<Array<String>
private fun SerialDescriptor.buildDeserializationNamesMap(json: Json): Map<String, Int> {
fun MutableMap<String, Int>.putOrThrow(name: String, index: Int) {
+ val entity = if (kind == SerialKind.ENUM) "enum value" else "property"
if (name in this) {
throw JsonException(
- "The suggested name '$name' for property ${getElementName(index)} is already one of the names for property " +
+ "The suggested name '$name' for $entity ${getElementName(index)} is already one of the names for $entity " +
"${getElementName(getValue(name))} in ${this@buildDeserializationNamesMap}"
)
}
@@ -28,12 +29,19 @@ private fun SerialDescriptor.buildDeserializationNamesMap(json: Json): Map<Strin
val builder: MutableMap<String, Int> =
mutableMapOf() // can be not concurrent because it is only read after creation and safely published to concurrent map
- val strategy = namingStrategy(json)
+ val useLowercaseEnums = json.decodeCaseInsensitive(this)
+ val strategyForClasses = namingStrategy(json)
for (i in 0 until elementsCount) {
getElementAnnotations(i).filterIsInstance<JsonNames>().singleOrNull()?.names?.forEach { name ->
- builder.putOrThrow(name, i)
+ builder.putOrThrow(if (useLowercaseEnums) name.lowercase() else name, i)
+ }
+ val nameToPut = when {
+ // the branches do not intersect because useLowercase = true for enums only, and strategy != null for classes only.
+ useLowercaseEnums -> getElementName(i).lowercase()
+ strategyForClasses != null -> strategyForClasses.serialNameForJson(this, i, getElementName(i))
+ else -> null
}
- strategy?.let { builder.putOrThrow(it.serialNameForJson(this, i, getElementName(i)), i) }
+ nameToPut?.let { builder.putOrThrow(it, i) }
}
return builder.ifEmpty { emptyMap() }
}
@@ -61,17 +69,24 @@ internal fun SerialDescriptor.getJsonElementName(json: Json, index: Int): String
internal fun SerialDescriptor.namingStrategy(json: Json) =
if (kind == StructureKind.CLASS) json.configuration.namingStrategy else null
+private fun SerialDescriptor.getJsonNameIndexSlowPath(json: Json, name: String): Int =
+ json.deserializationNamesMap(this)[name] ?: CompositeDecoder.UNKNOWN_NAME
+
+private fun Json.decodeCaseInsensitive(descriptor: SerialDescriptor) =
+ configuration.decodeEnumsCaseInsensitive && descriptor.kind == SerialKind.ENUM
+
/**
- * Serves same purpose as [SerialDescriptor.getElementIndex] but respects
- * [JsonNames] annotation and [JsonConfiguration.useAlternativeNames] state.
+ * Serves same purpose as [SerialDescriptor.getElementIndex] but respects [JsonNames] annotation
+ * and [JsonConfiguration] settings.
*/
@OptIn(ExperimentalSerializationApi::class)
internal fun SerialDescriptor.getJsonNameIndex(json: Json, name: String): Int {
- fun getJsonNameIndexSlowPath(): Int =
- json.deserializationNamesMap(this)[name] ?: CompositeDecoder.UNKNOWN_NAME
+ if (json.decodeCaseInsensitive(this)) {
+ return getJsonNameIndexSlowPath(json, name.lowercase())
+ }
val strategy = namingStrategy(json)
- if (strategy != null) return getJsonNameIndexSlowPath()
+ if (strategy != null) return getJsonNameIndexSlowPath(json, name)
val index = getElementIndex(name)
// Fast path, do not go through ConcurrentHashMap.get
// Note, it blocks ability to detect collisions between the primary name and alternate,
@@ -79,7 +94,7 @@ internal fun SerialDescriptor.getJsonNameIndex(json: Json, name: String): Int {
if (index != CompositeDecoder.UNKNOWN_NAME) return index
if (!json.configuration.useAlternativeNames) return index
// Slow path
- return getJsonNameIndexSlowPath()
+ return getJsonNameIndexSlowPath(json, name)
}
/**