summaryrefslogtreecommitdiff
path: root/formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonDecoder.kt
diff options
context:
space:
mode:
Diffstat (limited to 'formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonDecoder.kt')
-rw-r--r--formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonDecoder.kt99
1 files changed, 55 insertions, 44 deletions
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonDecoder.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonDecoder.kt
index 55e23a13..690b35e1 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonDecoder.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonDecoder.kt
@@ -15,11 +15,12 @@ import kotlinx.serialization.json.*
import kotlinx.serialization.modules.*
import kotlin.jvm.*
-internal fun <T> Json.readJson(element: JsonElement, deserializer: DeserializationStrategy<T>): T {
+@JsonFriendModuleApi
+public fun <T> readJson(json: Json, element: JsonElement, deserializer: DeserializationStrategy<T>): T {
val input = when (element) {
- is JsonObject -> JsonTreeDecoder(this, element)
- is JsonArray -> JsonTreeListDecoder(this, element)
- is JsonLiteral, JsonNull -> JsonPrimitiveDecoder(this, element as JsonPrimitive)
+ is JsonObject -> JsonTreeDecoder(json, element)
+ is JsonArray -> JsonTreeListDecoder(json, element)
+ is JsonLiteral, JsonNull -> JsonPrimitiveDecoder(json, element as JsonPrimitive)
}
return input.decodeSerializableValue(deserializer)
}
@@ -43,7 +44,7 @@ private sealed class AbstractJsonTreeDecoder(
@JvmField
protected val configuration = json.configuration
- private fun currentObject() = currentTagOrNull?.let { currentElement(it) } ?: value
+ protected fun currentObject() = currentTagOrNull?.let { currentElement(it) } ?: value
override fun decodeJsonElement(): JsonElement = currentObject()
@@ -90,16 +91,7 @@ private sealed class AbstractJsonTreeDecoder(
override fun decodeTaggedNotNullMark(tag: String): Boolean = currentElement(tag) !== JsonNull
override fun decodeTaggedBoolean(tag: String): Boolean {
- val value = getPrimitiveValue(tag)
- if (!json.configuration.isLenient) {
- val literal = value.asLiteral("boolean")
- if (literal.isString) throw JsonDecodingException(
- -1, "Boolean literal for key '$tag' should be unquoted.\n$lenientHint", currentObject().toString()
- )
- }
- return value.primitive("boolean") {
- booleanOrNull ?: throw IllegalArgumentException() /* Will be handled by 'primitive' */
- }
+ return getPrimitiveValue(tag).primitive("boolean", JsonPrimitive::booleanOrNull)
}
override fun decodeTaggedByte(tag: String) = getPrimitiveValue(tag).primitive("byte") {
@@ -133,7 +125,7 @@ private sealed class AbstractJsonTreeDecoder(
override fun decodeTaggedChar(tag: String): Char = getPrimitiveValue(tag).primitive("char") { content.single() }
- private inline fun <T: Any> JsonPrimitive.primitive(primitive: String, block: JsonPrimitive.() -> T?): T {
+ private inline fun <T : Any> JsonPrimitive.primitive(primitive: String, block: JsonPrimitive.() -> T?): T {
try {
return block() ?: unparsedPrimitive(primitive)
} catch (e: IllegalArgumentException) {
@@ -142,7 +134,7 @@ private sealed class AbstractJsonTreeDecoder(
}
private fun unparsedPrimitive(primitive: String): Nothing {
- throw JsonDecodingException(-1, "Failed to parse '$primitive'", currentObject().toString())
+ throw JsonDecodingException(-1, "Failed to parse literal as '$primitive' value", currentObject().toString())
}
override fun decodeTaggedString(tag: String): String {
@@ -158,16 +150,20 @@ private sealed class AbstractJsonTreeDecoder(
}
private fun JsonPrimitive.asLiteral(type: String): JsonLiteral {
- return this as? JsonLiteral ?: throw JsonDecodingException(-1, "Unexpected 'null' when $type was expected")
+ return this as? JsonLiteral ?: throw JsonDecodingException(-1, "Unexpected 'null' literal when non-nullable $type was expected")
}
- @OptIn(ExperimentalUnsignedTypes::class)
override fun decodeTaggedInline(tag: String, inlineDescriptor: SerialDescriptor): Decoder =
if (inlineDescriptor.isUnsignedNumber) JsonDecoderForUnsignedTypes(StringJsonLexer(getPrimitiveValue(tag).content), json)
else super.decodeTaggedInline(tag, inlineDescriptor)
+
+ override fun decodeInline(descriptor: SerialDescriptor): Decoder {
+ return if (currentTagOrNull != null) super.decodeInline(descriptor)
+ else JsonPrimitiveDecoder(json, value).decodeInline(descriptor)
+ }
}
-private class JsonPrimitiveDecoder(json: Json, override val value: JsonPrimitive) : AbstractJsonTreeDecoder(json, value) {
+private class JsonPrimitiveDecoder(json: Json, override val value: JsonElement) : AbstractJsonTreeDecoder(json, value) {
init {
pushTag(PRIMITIVE_TAG)
@@ -194,7 +190,7 @@ private open class JsonTreeDecoder(
*/
private fun coerceInputValue(descriptor: SerialDescriptor, index: Int, tag: String): Boolean =
json.tryCoerceValue(
- descriptor.getElementDescriptor(index),
+ descriptor, index,
{ currentElement(tag) is JsonNull },
{ (currentElement(tag) as? JsonPrimitive)?.contentOrNull }
)
@@ -224,40 +220,55 @@ private open class JsonTreeDecoder(
return !forceNull && super.decodeNotNullMark()
}
- override fun elementName(desc: SerialDescriptor, index: Int): String {
- val mainName = desc.getElementName(index)
- if (!configuration.useAlternativeNames) return mainName
- // Fast path, do not go through ConcurrentHashMap.get
- // Note, it blocks ability to detect collisions between the primary name and alternate,
- // but it eliminates a significant performance penalty (about -15% without this optimization)
- if (mainName in value.keys) return mainName
+ override fun elementName(descriptor: SerialDescriptor, index: Int): String {
+ val strategy = descriptor.namingStrategy(json)
+ val baseName = descriptor.getElementName(index)
+ if (strategy == null) {
+ if (!configuration.useAlternativeNames) return baseName
+ // Fast path, do not go through ConcurrentHashMap.get
+ // Note, it blocks ability to detect collisions between the primary name and alternate,
+ // but it eliminates a significant performance penalty (about -15% without this optimization)
+ if (baseName in value.keys) return baseName
+ }
// Slow path
- val alternativeNamesMap =
- json.schemaCache.getOrPut(desc, JsonAlternativeNamesKey, desc::buildAlternativeNamesMap)
- val nameInObject = value.keys.find { alternativeNamesMap[it] == index }
- return nameInObject ?: mainName
+ val deserializationNamesMap = json.deserializationNamesMap(descriptor)
+ value.keys.find { deserializationNamesMap[it] == index }?.let {
+ return it
+ }
+
+ val fallbackName = strategy?.serialNameForJson(
+ descriptor,
+ index,
+ baseName
+ ) // Key not found exception should be thrown with transformed name, not original
+ return fallbackName ?: baseName
}
override fun currentElement(tag: String): JsonElement = value.getValue(tag)
override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder {
- /*
- * For polymorphic serialization we'd like to avoid excessive decoder creating in
- * beginStructure to properly preserve 'polyDiscriminator' field and filter it out.
- */
- if (descriptor === polyDescriptor) return this
+ // polyDiscriminator needs to be preserved so the check for unknown keys
+ // in endStructure can filter polyDiscriminator out.
+ if (descriptor === polyDescriptor) {
+ return JsonTreeDecoder(
+ json, cast(currentObject(), polyDescriptor), polyDiscriminator, polyDescriptor
+ )
+ }
+
return super.beginStructure(descriptor)
}
override fun endStructure(descriptor: SerialDescriptor) {
if (configuration.ignoreUnknownKeys || descriptor.kind is PolymorphicKind) return
// Validate keys
+ val strategy = descriptor.namingStrategy(json)
+
@Suppress("DEPRECATION_ERROR")
- val names: Set<String> =
- if (!configuration.useAlternativeNames)
- descriptor.jsonCachedSerialNames()
- else
- descriptor.jsonCachedSerialNames() + json.schemaCache[descriptor, JsonAlternativeNamesKey]?.keys.orEmpty()
+ val names: Set<String> = when {
+ strategy == null && !configuration.useAlternativeNames -> descriptor.jsonCachedSerialNames()
+ strategy != null -> json.deserializationNamesMap(descriptor).keys
+ else -> descriptor.jsonCachedSerialNames() + json.schemaCache[descriptor, JsonDeserializationNamesKey]?.keys.orEmpty()
+ }
for (key in value.keys) {
if (key !in names && key != polyDiscriminator) {
@@ -272,7 +283,7 @@ private class JsonTreeMapDecoder(json: Json, override val value: JsonObject) : J
private val size: Int = keys.size * 2
private var position = -1
- override fun elementName(desc: SerialDescriptor, index: Int): String {
+ override fun elementName(descriptor: SerialDescriptor, index: Int): String {
val i = index / 2
return keys[i]
}
@@ -298,7 +309,7 @@ private class JsonTreeListDecoder(json: Json, override val value: JsonArray) : A
private val size = value.size
private var currentIndex = -1
- override fun elementName(desc: SerialDescriptor, index: Int): String = (index).toString()
+ override fun elementName(descriptor: SerialDescriptor, index: Int): String = index.toString()
override fun currentElement(tag: String): JsonElement {
return value[tag.toInt()]