Skip to content

Commit

Permalink
Null attempt
Browse files Browse the repository at this point in the history
  • Loading branch information
UnknownJoe796 committed Nov 21, 2023
1 parent 4d640f9 commit 8515728
Show file tree
Hide file tree
Showing 9 changed files with 319 additions and 34 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package kotlinx.serialization.csv.decode

import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerializationException
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.CompositeDecoder
import kotlinx.serialization.encoding.Decoder

@ExperimentalSerializationApi
public abstract class AbstractDecoderAlt : Decoder, CompositeDecoder {

/**
* Invoked to decode a value when specialized `encode*` method was not overridden.
*/
public open fun decodeValue(): Any = throw SerializationException("${this::class} can't retrieve untyped values")

override fun decodeNotNullMark(): Boolean = true
override fun decodeNull(): Nothing? = null
override fun decodeBoolean(): Boolean = decodeValue() as Boolean
override fun decodeByte(): Byte = decodeValue() as Byte
override fun decodeShort(): Short = decodeValue() as Short
override fun decodeInt(): Int = decodeValue() as Int
override fun decodeLong(): Long = decodeValue() as Long
override fun decodeFloat(): Float = decodeValue() as Float
override fun decodeDouble(): Double = decodeValue() as Double
override fun decodeChar(): Char = decodeValue() as Char
override fun decodeString(): String = decodeValue() as String
override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = decodeValue() as Int

// overwrite by default
public open fun <T : Any?> decodeSerializableValue(
deserializer: DeserializationStrategy<T>,
previousValue: T? = null
): T = decodeSerializableValue(deserializer)

override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder = this

override fun endStructure(descriptor: SerialDescriptor) {
}

final override fun decodeBooleanElement(descriptor: SerialDescriptor, index: Int): Boolean = decodeBoolean()
final override fun decodeByteElement(descriptor: SerialDescriptor, index: Int): Byte = decodeByte()
final override fun decodeShortElement(descriptor: SerialDescriptor, index: Int): Short = decodeShort()
final override fun decodeIntElement(descriptor: SerialDescriptor, index: Int): Int = decodeInt()
final override fun decodeLongElement(descriptor: SerialDescriptor, index: Int): Long = decodeLong()
final override fun decodeFloatElement(descriptor: SerialDescriptor, index: Int): Float = decodeFloat()
final override fun decodeDoubleElement(descriptor: SerialDescriptor, index: Int): Double = decodeDouble()
final override fun decodeCharElement(descriptor: SerialDescriptor, index: Int): Char = decodeChar()
final override fun decodeStringElement(descriptor: SerialDescriptor, index: Int): String = decodeString()

final override fun <T> decodeSerializableElement(
descriptor: SerialDescriptor,
index: Int,
deserializer: DeserializationStrategy<T>,
previousValue: T?
): T = decodeSerializableValue(deserializer, previousValue)

override fun <T : Any> decodeNullableSerializableElement(
descriptor: SerialDescriptor,
index: Int,
deserializer: DeserializationStrategy<T?>,
previousValue: T?
): T? {
val isNullabilitySupported = deserializer.descriptor.isNullable
return if (isNullabilitySupported || decodeNotNullMark()) decodeSerializableValue(deserializer, previousValue) else decodeNull()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,19 @@ internal class ClassCsvDecoder(
elementIndex >= descriptor.elementsCount -> DECODE_DONE
classHeaders != null && columnIndex >= classHeaders.size -> DECODE_DONE

classHeaders != null ->
classHeaders != null -> {
when (val result = classHeaders[columnIndex]) {
UNKNOWN_NAME -> {
ignoreColumn()
decodeElementIndex(descriptor)
}

null -> UNKNOWN_NAME
else -> result
else -> result.also { println("${descriptor.serialName} decoded ${it} (${descriptor.getElementName(it)}) due to column ${columnIndex} in ${classHeaders.toString(descriptor)}") }
}
}

else -> elementIndex
else -> elementIndex.also { println("${descriptor.serialName} decoded ${it} (${descriptor.getElementName(it)})") }
}

override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder {
Expand Down Expand Up @@ -84,4 +86,9 @@ internal class ClassCsvDecoder(
reader.readColumn()
columnIndex++
}

override fun virtualColumnAdvance() {
columnIndex++
elementIndex++
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ package kotlinx.serialization.csv.decode

import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.nullable
import kotlinx.serialization.csv.Csv
import kotlinx.serialization.csv.HeadersNotSupportedForSerialDescriptorException
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
Expand All @@ -24,7 +24,7 @@ internal abstract class CsvDecoder(
protected val csv: Csv,
protected val reader: CsvReader,
private val parent: CsvDecoder?
) : AbstractDecoder() {
) : AbstractDecoderAlt() {

override val serializersModule: SerializersModule
get() = csv.serializersModule
Expand Down Expand Up @@ -111,11 +111,18 @@ internal abstract class CsvDecoder(
return null
}

fun skipEmpty(): Nothing? {
val value = reader.readColumn()
println("SKIPPING $value")
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 open fun decodeColumn() = reader.readColumn().also { println("READ COLUMN $it") }

protected fun readHeaders(descriptor: SerialDescriptor) {
if (config.hasHeaderRecord && headers == null) {
Expand Down Expand Up @@ -144,6 +151,7 @@ internal abstract class CsvDecoder(
val headerIndex = descriptor.getElementIndex(header)
if (headerIndex != UNKNOWN_NAME) {
headers[position] = headerIndex
println("SET $position TO $header")
reader.unmark()
} else {
val name = header.substringBefore(config.headerSeparator)
Expand All @@ -152,7 +160,6 @@ internal abstract class CsvDecoder(
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()
Expand All @@ -168,7 +175,7 @@ internal abstract class CsvDecoder(
}
position++
}
return headers
return headers.also { println(it.toString(descriptor)) }
}

protected fun readTrailingDelimiter() {
Expand Down Expand Up @@ -197,18 +204,74 @@ internal abstract class CsvDecoder(
operator fun set(key: Int, value: Headers) {
subHeaders[key] = value
}

override fun toString(): String {
return "Headers(map=${map}, subHeaders=${subHeaders})"
}
fun toString(context: SerialDescriptor): String {
return "Headers(map=${map.mapValues { if(it.value in 0 until context.elementsCount) context.getElementName(it.value) else "???" }})"
}
}

override fun <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T {
return if (config.deferToFormatWhenVariableColumns != null) {
if (config.deferToFormatWhenVariableColumns != null) {
when (deserializer.descriptor.kind) {
is StructureKind.LIST,
is StructureKind.MAP,
is PolymorphicKind.OPEN -> {
config.deferToFormatWhenVariableColumns!!.decodeFromString(deserializer, decodeColumn())
return config.deferToFormatWhenVariableColumns!!.decodeFromString(deserializer, decodeColumn())
}
else -> {}
}
}
if(deserializer.descriptor.isNullable && deserializer.descriptor.kind == StructureKind.CLASS && deserializer.descriptor.elementsCount > 0) {
val isPresent = reader.readColumn().toBoolean()
println("READ PRESENT $isPresent")
if(isPresent) {
return deserializer.deserialize(this)
} else {
virtualColumnAdvance()
decodeNulls(deserializer.descriptor, deserializer.descriptor.serialName)
@Suppress("UNCHECKED_CAST")
return null as T
}
} else {
return deserializer.deserialize(this)
}
}

protected open fun virtualColumnAdvance() {}

private fun decodeNulls(serializer: SerialDescriptor, name: String) {
if(serializer.kind == StructureKind.CLASS) {
for(index in (0 until serializer.elementsCount)) {
val sub = serializer.getElementDescriptor(index)
if(sub.isNullable) {
println("Skipping present ${serializer.getElementName(index)}")
skipEmpty()
}
else -> super.decodeSerializableValue(deserializer)
decodeNulls(sub, serializer.getElementName(index))
}
} else super.decodeSerializableValue(deserializer)
println("Skipping ${name} end")
} else {
println("Skipping $name")
skipEmpty()
}
}

override fun <T : Any> decodeNullableSerializableValue(deserializer: DeserializationStrategy<T?>): T? {
val isNullabilitySupported = deserializer.descriptor.isNullable
return if (isNullabilitySupported || decodeNotNullMark()) decodeSerializableValue(deserializer) else decodeSerializableValue((deserializer as KSerializer<T>).nullable)
}

override fun <T : Any> decodeNullableSerializableElement(
descriptor: SerialDescriptor,
index: Int,
deserializer: DeserializationStrategy<T?>,
previousValue: T?
): T? {
val isNullabilitySupported = deserializer.descriptor.isNullable
return if (isNullabilitySupported) decodeSerializableValue(deserializer, previousValue) else decodeSerializableValue((deserializer as KSerializer<T>).nullable, previousValue)
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package kotlinx.serialization.csv.decode

import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.nullable
import kotlinx.serialization.csv.Csv
import kotlinx.serialization.descriptors.PolymorphicKind
import kotlinx.serialization.descriptors.SerialDescriptor
Expand Down Expand Up @@ -55,13 +57,25 @@ internal class RootCsvDecoder(
}

override fun <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T {
return if (config.deferToFormatWhenVariableColumns != null) {
if (config.deferToFormatWhenVariableColumns != null) {
when (deserializer.descriptor.kind) {
is PolymorphicKind.OPEN -> {
config.deferToFormatWhenVariableColumns!!.decodeFromString(deserializer, decodeColumn())
}
else -> deserializer.deserialize(this)
else -> {}
}
} else deserializer.deserialize(this)
}
if(deserializer.descriptor.isNullable && deserializer.descriptor.kind == StructureKind.CLASS && deserializer.descriptor.elementsCount > 0) {
val isPresent = decodeBoolean()
println("Decoded present boolean ${isPresent}")
if(isPresent) {
return deserializer.deserialize(this)
} else {
@Suppress("UNCHECKED_CAST")
return null as T
}
} else {
return deserializer.deserialize(this)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package kotlinx.serialization.csv.encode

import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.builtins.nullable
import kotlinx.serialization.csv.Csv
import kotlinx.serialization.csv.HeadersNotSupportedForSerialDescriptorException
import kotlinx.serialization.csv.UnsupportedSerialDescriptorException
import kotlinx.serialization.descriptors.PolymorphicKind
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.descriptors.SerialKind
import kotlinx.serialization.descriptors.StructureKind
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.AbstractEncoder
import kotlinx.serialization.encoding.CompositeEncoder
import kotlinx.serialization.modules.SerializersModule
Expand Down Expand Up @@ -146,6 +145,7 @@ internal abstract class CsvEncoder(
Unit

childDesc.elementsCount > 0 -> {
if(childDesc.isNullable) writer.printColumn(name)
val headerSeparator = config.headerSeparator
printHeader("$name$headerSeparator", childDesc)
}
Expand Down Expand Up @@ -179,9 +179,44 @@ internal abstract class CsvEncoder(
isNumeric = false,
isNull = false
)
return
}
else -> super.encodeSerializableValue(serializer, value)
else -> {}
}
} else super.encodeSerializableValue(serializer, value)
}
if(serializer.descriptor.isNullable && serializer.descriptor.kind == StructureKind.CLASS && serializer.descriptor.elementsCount > 0) {
if(value == null) {
encodeBoolean(false)
encodeNulls(serializer.descriptor)
} else {
encodeBoolean(true)
serializer.serialize(this, value)
}
} else {
serializer.serialize(this, value)
}
}

private fun encodeNulls(serializer: SerialDescriptor) {
if(serializer.kind == StructureKind.CLASS) {
for(index in (0 until serializer.elementsCount)) {
val sub = serializer.getElementDescriptor(index)
if(sub.isNullable) {
encodeNull()
}
encodeNulls(sub)
}
} else {
encodeNull()
}
}

override fun <T : Any> encodeNullableSerializableValue(serializer: SerializationStrategy<T>, value: T?) {
val isNullabilitySupported = serializer.descriptor.isNullable
if (isNullabilitySupported) {
// Instead of `serializer.serialize` to be able to intercept this
return encodeSerializableValue(serializer as SerializationStrategy<T?>, value)
}
return encodeSerializableValue((serializer as KSerializer<T>).nullable, value)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ class CsvHasHeaderRecordTest {
0.0,
1.0
), 100, "info"
)
),
"Albert"
),
NestedRecord.serializer()
)
Expand All @@ -143,7 +144,8 @@ class CsvHasHeaderRecordTest {
0.0,
1.0
), 100, "info"
)
),
"Albert"
),
NestedRecord(
1,
Expand All @@ -153,7 +155,8 @@ class CsvHasHeaderRecordTest {
10.0,
20.0
), 50, "info2"
)
),
"Bill"
)
),
ListSerializer(NestedRecord.serializer())
Expand Down
Loading

0 comments on commit 8515728

Please sign in to comment.