From 35609a1a6733057f5ef970620fdd67c99a0c99e4 Mon Sep 17 00:00:00 2001 From: Chuckame Date: Fri, 26 Apr 2024 17:41:23 +0200 Subject: [PATCH] fix: Allow encoding null array items or null map values --- .../avrokotlin/avro4k/decoder/ListDecoder.kt | 1 + .../avrokotlin/avro4k/decoder/MapDecoder.kt | 8 ++ .../avrokotlin/avro4k/encoder/ListEncoder.kt | 4 + .../avrokotlin/avro4k/encoder/MapEncoder.kt | 10 +- .../avro4k/endecode/ArrayEncoderTest.kt | 93 ++++++++++--------- .../avro4k/endecode/MapEncoderTest.kt | 9 ++ 6 files changed, 81 insertions(+), 44 deletions(-) diff --git a/src/main/kotlin/com/github/avrokotlin/avro4k/decoder/ListDecoder.kt b/src/main/kotlin/com/github/avrokotlin/avro4k/decoder/ListDecoder.kt index 89123297..e529b20e 100644 --- a/src/main/kotlin/com/github/avrokotlin/avro4k/decoder/ListDecoder.kt +++ b/src/main/kotlin/com/github/avrokotlin/avro4k/decoder/ListDecoder.kt @@ -71,6 +71,7 @@ class ListDecoder( return array[index] } + override fun decodeNotNullMark() = array[index] != null override fun fieldSchema(): Schema = schema.elementType override fun decodeEnum(enumDescriptor: SerialDescriptor): Int { diff --git a/src/main/kotlin/com/github/avrokotlin/avro4k/decoder/MapDecoder.kt b/src/main/kotlin/com/github/avrokotlin/avro4k/decoder/MapDecoder.kt index d9e08136..14f4d3a1 100644 --- a/src/main/kotlin/com/github/avrokotlin/avro4k/decoder/MapDecoder.kt +++ b/src/main/kotlin/com/github/avrokotlin/avro4k/decoder/MapDecoder.kt @@ -42,6 +42,14 @@ class MapDecoder( private fun value(): Any? = entries[index / 2].second + override fun decodeNotNullMark() : Boolean { + val entry = entries[index / 2] + return when { + index % 2 == 0 -> entry.first + else -> entry.second + } != null + } + override fun decodeFloat(): Float { return when (val v = value()) { is Float -> v diff --git a/src/main/kotlin/com/github/avrokotlin/avro4k/encoder/ListEncoder.kt b/src/main/kotlin/com/github/avrokotlin/avro4k/encoder/ListEncoder.kt index bf4b7eb1..34211c37 100644 --- a/src/main/kotlin/com/github/avrokotlin/avro4k/encoder/ListEncoder.kt +++ b/src/main/kotlin/com/github/avrokotlin/avro4k/encoder/ListEncoder.kt @@ -32,6 +32,10 @@ class ListEncoder(private val schema: Schema, list.add(value) } + override fun encodeNull() { + list.add(null) + } + override fun encodeString(value: String) { list.add(StringToAvroValue.toValue(schema, value)) } diff --git a/src/main/kotlin/com/github/avrokotlin/avro4k/encoder/MapEncoder.kt b/src/main/kotlin/com/github/avrokotlin/avro4k/encoder/MapEncoder.kt index d1ca17fd..11776dfb 100644 --- a/src/main/kotlin/com/github/avrokotlin/avro4k/encoder/MapEncoder.kt +++ b/src/main/kotlin/com/github/avrokotlin/avro4k/encoder/MapEncoder.kt @@ -19,7 +19,7 @@ class MapEncoder(schema: Schema, CompositeEncoder, StructureEncoder { - private val map = mutableMapOf() + private val map = mutableMapOf() private var key: Utf8? = null private val valueSchema = schema.valueType @@ -39,6 +39,14 @@ class MapEncoder(schema: Schema, } } + override fun encodeNull() { + val k = key + if (k == null) throw SerializationException("Expected key but received null value") else { + map[k] = null + key = null + } + } + override fun endStructure(descriptor: SerialDescriptor) { callback(map.toMap()) } diff --git a/src/test/kotlin/com/github/avrokotlin/avro4k/endecode/ArrayEncoderTest.kt b/src/test/kotlin/com/github/avrokotlin/avro4k/endecode/ArrayEncoderTest.kt index ff525f00..12b75a05 100644 --- a/src/test/kotlin/com/github/avrokotlin/avro4k/endecode/ArrayEncoderTest.kt +++ b/src/test/kotlin/com/github/avrokotlin/avro4k/endecode/ArrayEncoderTest.kt @@ -7,51 +7,58 @@ import io.kotest.core.spec.style.wordSpec import kotlinx.serialization.Serializable class ArrayEncoderTest : WordSpec({ - includeForEveryEncoder { arrayEncodingTests(it) } + includeForEveryEncoder { arrayEncodingTests(it) } }) @Suppress("ArrayInDataClass") fun arrayEncodingTests(encoderToTest: EnDecoder): TestFactory { - return wordSpec { - "en-/decoder" should { - "generate GenericData.Array for an Array" { - @Serializable - data class ArrayBooleanTest(val a: Array) - - val value = ArrayBooleanTest(arrayOf(true, false, true)) - encoderToTest.testEncodeDecode(value, record(value.a.asList())) - } - "support GenericData.Array for an Array with other fields" { - @Serializable - data class ArrayBooleanWithOthersTest(val a: String, val b: Array, val c: Long) - - val value = ArrayBooleanWithOthersTest("foo", arrayOf(true, false, true), 123L) - encoderToTest.testEncodeDecode( - value, record( - "foo", - listOf(true, false, true), - 123L - ) - ) - } - - "generate GenericData.Array for a List" { - @Serializable - data class ListStringTest(val a: List) - encoderToTest.testEncodeDecode( - ListStringTest(listOf("we23", "54z")), record( - listOf("we23", "54z") - ) - ) - } - "generate GenericData.Array for a Set" { - @Serializable - data class SetLongTest(val a: Set) - - val value = SetLongTest(setOf(123L, 643L, 912L)) - val record = record(listOf(123L, 643L, 912)) - encoderToTest.testEncodeDecode(value, record) - } - } - } + return wordSpec { + "en-/decoder" should { + "generate GenericData.Array for an Array" { + @Serializable + data class ArrayBooleanTest(val a: Array) + + val value = ArrayBooleanTest(arrayOf(true, false, true)) + encoderToTest.testEncodeDecode(value, record(value.a.asList())) + } + "generate GenericData.Array for an Array" { + @Serializable + data class ArrayBooleanTest(val a: Array) + + val value = ArrayBooleanTest(arrayOf(true, null, false)) + encoderToTest.testEncodeDecode(value, record(value.a.asList())) + } + "support GenericData.Array for an Array with other fields" { + @Serializable + data class ArrayBooleanWithOthersTest(val a: String, val b: Array, val c: Long) + + val value = ArrayBooleanWithOthersTest("foo", arrayOf(true, false, true), 123L) + encoderToTest.testEncodeDecode( + value, record( + "foo", + listOf(true, false, true), + 123L + ) + ) + } + + "generate GenericData.Array for a List" { + @Serializable + data class ListStringTest(val a: List) + encoderToTest.testEncodeDecode( + ListStringTest(listOf("we23", "54z")), record( + listOf("we23", "54z") + ) + ) + } + "generate GenericData.Array for a Set" { + @Serializable + data class SetLongTest(val a: Set) + + val value = SetLongTest(setOf(123L, 643L, 912L)) + val record = record(listOf(123L, 643L, 912)) + encoderToTest.testEncodeDecode(value, record) + } + } + } } \ No newline at end of file diff --git a/src/test/kotlin/com/github/avrokotlin/avro4k/endecode/MapEncoderTest.kt b/src/test/kotlin/com/github/avrokotlin/avro4k/endecode/MapEncoderTest.kt index 61f94eea..aa568b5e 100644 --- a/src/test/kotlin/com/github/avrokotlin/avro4k/endecode/MapEncoderTest.kt +++ b/src/test/kotlin/com/github/avrokotlin/avro4k/endecode/MapEncoderTest.kt @@ -23,6 +23,15 @@ fun mapEncoderTests(enDecoder: EnDecoder): TestFactory { ) } + "encode/decode a Map" { + @Serializable + data class StringBooleanTest(val a: Map) + enDecoder.testEncodeDecode( + StringBooleanTest(mapOf("a" to true, "b" to null, "c" to false)), + record(mapOf("a" to true, "b" to null, "c" to false)) + ) + } + "encode/decode a Map" { @Serializable