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

kotlinx.serialization.csv.decode.CsvDecoder.kt Maven / Gradle / Ivy

package kotlinx.serialization.csv.decode

import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.csv.Csv
import kotlinx.serialization.csv.UnknownColumnHeaderException
import kotlinx.serialization.csv.UnsupportedSerialDescriptorException
import kotlinx.serialization.csv.config.CsvConfig
import kotlinx.serialization.descriptors.PolymorphicKind
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.descriptors.StructureKind
import kotlinx.serialization.encoding.AbstractDecoder
import kotlinx.serialization.encoding.CompositeDecoder
import kotlinx.serialization.encoding.CompositeDecoder.Companion.UNKNOWN_NAME
import kotlinx.serialization.modules.SerializersModule
import kotlin.collections.set

/**
 * Default CSV decoder.
 */
@OptIn(ExperimentalSerializationApi::class)
internal abstract class CsvDecoder(
    protected val csv: Csv,
    protected val reader: CsvReader,
    private val parent: CsvDecoder?
) : AbstractDecoder() {

    override val serializersModule: SerializersModule
        get() = csv.serializersModule

    protected val config: CsvConfig
        get() = csv.config

    private var headers: Headers? = null

    override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder {
        return when (descriptor.kind) {
            StructureKind.LIST,
            StructureKind.MAP ->
                CollectionCsvDecoder(csv, reader, this)

            StructureKind.CLASS ->
                ClassCsvDecoder(csv, reader, this, headers)

            StructureKind.OBJECT ->
                ObjectCsvDecoder(csv, reader, this)

            PolymorphicKind.SEALED ->
                SealedCsvDecoder(csv, reader, this, descriptor)

            PolymorphicKind.OPEN ->
                ClassCsvDecoder(csv, reader, this, headers)

            else -> throw UnsupportedSerialDescriptorException(descriptor)
        }
    }

    override fun endStructure(descriptor: SerialDescriptor) {
        parent?.endChildStructure(descriptor)
    }

    protected open fun endChildStructure(descriptor: SerialDescriptor) {
    }

    override fun decodeByte(): Byte {
        return decodeColumn().toByte()
    }

    override fun decodeShort(): Short {
        return decodeColumn().toShort()
    }

    override fun decodeInt(): Int {
        return decodeColumn().toInt()
    }

    override fun decodeLong(): Long {
        return decodeColumn().toLong()
    }

    override fun decodeFloat(): Float {
        return decodeColumn().toFloat()
    }

    override fun decodeDouble(): Double {
        return decodeColumn().toDouble()
    }

    override fun decodeBoolean(): Boolean {
        return decodeColumn().toBoolean()
    }

    override fun decodeChar(): Char {
        val value = decodeColumn()
        require(value.length == 1)
        return value[0]
    }

    override fun decodeString(): String {
        return decodeColumn()
    }

    override fun decodeNotNullMark(): Boolean {
        return !reader.isNullToken()
    }

    override fun decodeNull(): Nothing? {
        val value = decodeColumn()
        require(value == config.nullString) { "Expected '${config.nullString}' but was '$value'." }
        return null
    }

    override fun decodeEnum(enumDescriptor: SerialDescriptor): Int {
        return enumDescriptor.getElementIndex(decodeColumn())
    }

    protected open fun decodeColumn() = reader.readColumn()

    protected fun readHeaders(descriptor: SerialDescriptor) {
        if (config.hasHeaderRecord && headers == null) {
            this.headers = readHeaders(descriptor, "")

            readTrailingDelimiter()
        }
    }

    private fun readHeaders(descriptor: SerialDescriptor, prefix: String): Headers {
        val headers = Headers()
        var position = 0
        while (!reader.isDone && reader.isFirstRecord) {
            val offset = reader.offset
            reader.mark()

            // Read header value and check if it (still) starts with required prefix
            val value = reader.readColumn()
            if (!value.startsWith(prefix)) {
                reader.reset()
                break
            }

            // If there is an exact name match, store the header, otherwise try reading class structure
            val header = value.substringAfter(prefix)
            val headerIndex = descriptor.getElementIndex(header)
            if (headerIndex != UNKNOWN_NAME) {
                headers[position] = headerIndex
                reader.unmark()
            } else {
                val name = header.substringBefore(config.headerSeparator)
                val nameIndex = descriptor.getElementIndex(name)
                if (nameIndex != UNKNOWN_NAME) {
                    val childDesc = descriptor.getElementDescriptor(nameIndex)
                    if (childDesc.kind is StructureKind.CLASS) {
                        reader.reset()
                        headers[position] = nameIndex
                        headers[nameIndex] = readHeaders(childDesc, "$prefix$name.")
                    } else {
                        reader.unmark()
                    }
                } else if (csv.config.ignoreUnknownColumns) {
                    headers[position] = UNKNOWN_NAME
                    reader.unmark()
                } else if (value == "" && !reader.isFirstRecord && config.hasTrailingDelimiter) {
                    reader.unmark()
                } else {
                    throw UnknownColumnHeaderException(offset, value)
                }
            }
            position++
        }
        return headers
    }

    protected fun readTrailingDelimiter() {
        if (config.hasTrailingDelimiter) {
            reader.readEndOfRecord()
        }
    }

    internal class Headers {
        private val map = mutableMapOf()
        private val subHeaders = mutableMapOf()

        val size
            get() = map.size

        operator fun get(position: Int) =
            map[position]

        operator fun set(key: Int, value: Int) {
            map[key] = value
        }

        fun getSubHeaders(position: Int) =
            subHeaders.getOrElse(position) { null }

        operator fun set(key: Int, value: Headers) {
            subHeaders[key] = value
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy