All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.kotlinx.serialization.json.internal.TreeJsonEncoder.kt Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
 */

package kotlinx.serialization.json.internal

import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.internal.*
import kotlinx.serialization.json.*
import kotlinx.serialization.modules.*
import kotlin.collections.set
import kotlin.jvm.*

internal fun  Json.writeJson(value: T, serializer: SerializationStrategy): JsonElement {
    lateinit var result: JsonElement
    val encoder = JsonTreeEncoder(this) { result = it }
    encoder.encodeSerializableValue(serializer, value)
    return result
}

private sealed class AbstractJsonTreeEncoder(
    final override val json: Json,
    val nodeConsumer: (JsonElement) -> Unit
) : NamedValueEncoder(), JsonEncoder {

    final override val serializersModule: SerializersModule
        get() = json.serializersModule

    @JvmField
    protected val configuration = json.configuration

    private var writePolymorphic = false

    override fun encodeJsonElement(element: JsonElement) {
        encodeSerializableValue(JsonElementSerializer, element)
    }

    override fun shouldEncodeElementDefault(descriptor: SerialDescriptor, index: Int): Boolean = configuration.encodeDefaults
    override fun composeName(parentName: String, childName: String): String = childName
    abstract fun putElement(key: String, element: JsonElement)
    abstract fun getCurrent(): JsonElement

    override fun encodeTaggedNull(tag: String) = putElement(tag, JsonNull)

    override fun encodeTaggedInt(tag: String, value: Int) = putElement(tag, JsonPrimitive(value))
    override fun encodeTaggedByte(tag: String, value: Byte) = putElement(tag, JsonPrimitive(value))
    override fun encodeTaggedShort(tag: String, value: Short) = putElement(tag, JsonPrimitive(value))
    override fun encodeTaggedLong(tag: String, value: Long) = putElement(tag, JsonPrimitive(value))

    override fun encodeTaggedFloat(tag: String, value: Float) {
        // First encode value, then check, to have a prettier error message
        putElement(tag, JsonPrimitive(value))
        if (!configuration.allowSpecialFloatingPointValues && !value.isFinite()) {
            throw InvalidFloatingPoint(value, tag, "float", getCurrent().toString())
        }
    }

    override fun  encodeSerializableValue(serializer: SerializationStrategy, value: T) {
        // Writing non-structured data (i.e. primitives) on top-level (e.g. without any tag) requires special output
        if (currentTagOrNull != null || serializer.descriptor.kind !is PrimitiveKind && serializer.descriptor.kind !== SerialKind.ENUM) {
            encodePolymorphically(serializer, value) { writePolymorphic = true }
        } else JsonPrimitiveEncoder(json, nodeConsumer).apply {
            encodeSerializableValue(serializer, value)
            endEncode(serializer.descriptor)
        }
    }

    override fun encodeTaggedDouble(tag: String, value: Double) {
        // First encode value, then check, to have a prettier error message
        putElement(tag, JsonPrimitive(value))
        if (!configuration.allowSpecialFloatingPointValues && !value.isFinite()) {
            throw InvalidFloatingPoint(value, tag, "double", getCurrent().toString())
        }
    }

    override fun encodeTaggedBoolean(tag: String, value: Boolean) = putElement(tag, JsonPrimitive(value))
    override fun encodeTaggedChar(tag: String, value: Char) = putElement(tag, JsonPrimitive(value.toString()))
    override fun encodeTaggedString(tag: String, value: String) = putElement(tag, JsonPrimitive(value))
    override fun encodeTaggedEnum(
        tag: String,
        enumDescription: SerialDescriptor,
        ordinal: Int
    ) = putElement(tag, JsonPrimitive(enumDescription.getElementName(ordinal)))

    override fun encodeTaggedValue(tag: String, value: Any) {
        putElement(tag, JsonPrimitive(value.toString()))
    }

    override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder {
        val consumer =
            if (currentTagOrNull == null) nodeConsumer
            else { node -> putElement(currentTag, node) }

        val encoder = when (descriptor.kind) {
            StructureKind.LIST, is PolymorphicKind -> JsonTreeListEncoder(json, consumer)
            StructureKind.MAP -> json.selectMapMode(
                descriptor,
                { JsonTreeMapEncoder(json, consumer) },
                { JsonTreeListEncoder(json, consumer) }
            )
            else -> JsonTreeEncoder(json, consumer)
        }

        if (writePolymorphic) {
            writePolymorphic = false
            encoder.putElement(configuration.classDiscriminator, JsonPrimitive(descriptor.serialName))
        }

        return encoder
    }

    override fun endEncode(descriptor: SerialDescriptor) {
        nodeConsumer(getCurrent())
    }
}

internal const val PRIMITIVE_TAG = "primitive" // also used in JsonPrimitiveInput

private class JsonPrimitiveEncoder(json: Json, nodeConsumer: (JsonElement) -> Unit) :
    AbstractJsonTreeEncoder(json, nodeConsumer) {
    private var content: JsonElement? = null

    init {
        pushTag(PRIMITIVE_TAG)
    }

    override fun putElement(key: String, element: JsonElement) {
        require(key === PRIMITIVE_TAG) { "This output can only consume primitives with '$PRIMITIVE_TAG' tag" }
        require(content == null) { "Primitive element was already recorded. Does call to .encodeXxx happen more than once?" }
        content = element
    }

    override fun getCurrent(): JsonElement =
        requireNotNull(content) { "Primitive element has not been recorded. Is call to .encodeXxx is missing in serializer?" }
}

private open class JsonTreeEncoder(
    json: Json, nodeConsumer: (JsonElement) -> Unit
) : AbstractJsonTreeEncoder(json, nodeConsumer) {

    protected val content: MutableMap = linkedMapOf()

    override fun putElement(key: String, element: JsonElement) {
        content[key] = element
    }

    override fun getCurrent(): JsonElement = JsonObject(content)
}

private class JsonTreeMapEncoder(json: Json, nodeConsumer: (JsonElement) -> Unit) : JsonTreeEncoder(json, nodeConsumer) {
    private lateinit var tag: String
    private var isKey = true

    override fun putElement(key: String, element: JsonElement) {
        if (isKey) { // writing key
            tag = when (element) {
                is JsonPrimitive -> element.content
                is JsonObject -> throw InvalidKeyKindException(JsonObjectSerializer.descriptor)
                is JsonArray -> throw InvalidKeyKindException(JsonArraySerializer.descriptor)
            }
            isKey = false
        } else {
            content[tag] = element
            isKey = true
        }
    }

    override fun getCurrent(): JsonElement {
        return JsonObject(content)
    }
}

private class JsonTreeListEncoder(json: Json, nodeConsumer: (JsonElement) -> Unit) :
    AbstractJsonTreeEncoder(json, nodeConsumer) {
    private val array: ArrayList = arrayListOf()
    override fun elementName(descriptor: SerialDescriptor, index: Int): String = index.toString()

    override fun putElement(key: String, element: JsonElement) {
        val idx = key.toInt()
        array.add(idx, element)
    }

    override fun getCurrent(): JsonElement = JsonArray(array)
}

internal inline fun  cast(value: JsonElement, descriptor: SerialDescriptor): T {
    if (value !is T) {
        throw JsonDecodingException(-1, "Expected ${T::class} as the serialized body of ${descriptor.serialName}, but had ${value::class}")
    }
    return value
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy