Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support Any type (encoding from & decoding to generic data) #260

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 59 additions & 1 deletion api/avro4k-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public abstract class com/github/avrokotlin/avro4k/Avro : kotlinx/serialization/
public fun encodeToByteArray (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)[B
public final fun encodeToByteArray (Lorg/apache/avro/Schema;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)[B
public final fun getConfiguration ()Lcom/github/avrokotlin/avro4k/AvroConfiguration;
public fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule;
public final fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule;
public final fun schema (Lkotlinx/serialization/descriptors/SerialDescriptor;)Lorg/apache/avro/Schema;
}

Expand Down Expand Up @@ -73,6 +73,7 @@ public abstract interface class com/github/avrokotlin/avro4k/AvroDecoder : kotli
public abstract fun decodeBytes ()[B
public abstract fun decodeFixed ()Lorg/apache/avro/generic/GenericFixed;
public abstract fun decodeValue ()Ljava/lang/Object;
public abstract fun getAvro ()Lcom/github/avrokotlin/avro4k/Avro;
public abstract fun getCurrentWriterSchema ()Lorg/apache/avro/Schema;
}

Expand Down Expand Up @@ -117,6 +118,7 @@ public abstract interface class com/github/avrokotlin/avro4k/AvroEncoder : kotli
public abstract fun encodeBytes ([B)V
public abstract fun encodeFixed (Lorg/apache/avro/generic/GenericFixed;)V
public abstract fun encodeFixed ([B)V
public abstract fun getAvro ()Lcom/github/avrokotlin/avro4k/Avro;
public abstract fun getCurrentWriterSchema ()Lorg/apache/avro/Schema;
}

Expand Down Expand Up @@ -414,6 +416,15 @@ public final class com/github/avrokotlin/avro4k/serializer/BigIntegerSerializer
public fun serializeGeneric (Lkotlinx/serialization/encoding/Encoder;Ljava/math/BigInteger;)V
}

public final class com/github/avrokotlin/avro4k/serializer/ByteBufferSerializer : com/github/avrokotlin/avro4k/serializer/AvroSerializer {
public static final field INSTANCE Lcom/github/avrokotlin/avro4k/serializer/ByteBufferSerializer;
public synthetic fun deserializeAvro (Lcom/github/avrokotlin/avro4k/AvroDecoder;)Ljava/lang/Object;
public fun deserializeAvro (Lcom/github/avrokotlin/avro4k/AvroDecoder;)Ljava/nio/ByteBuffer;
public fun getSchema (Lcom/github/avrokotlin/avro4k/serializer/SchemaSupplierContext;)Lorg/apache/avro/Schema;
public synthetic fun serializeAvro (Lcom/github/avrokotlin/avro4k/AvroEncoder;Ljava/lang/Object;)V
public fun serializeAvro (Lcom/github/avrokotlin/avro4k/AvroEncoder;Ljava/nio/ByteBuffer;)V
}

public final class com/github/avrokotlin/avro4k/serializer/ElementLocation {
public fun <init> (Lkotlinx/serialization/descriptors/SerialDescriptor;I)V
public final fun component1 ()Lkotlinx/serialization/descriptors/SerialDescriptor;
Expand All @@ -427,6 +438,53 @@ public final class com/github/avrokotlin/avro4k/serializer/ElementLocation {
public fun toString ()Ljava/lang/String;
}

public final class com/github/avrokotlin/avro4k/serializer/GenericArraySerializer : com/github/avrokotlin/avro4k/serializer/AvroSerializer {
public static final field INSTANCE Lcom/github/avrokotlin/avro4k/serializer/GenericArraySerializer;
public synthetic fun deserializeAvro (Lcom/github/avrokotlin/avro4k/AvroDecoder;)Ljava/lang/Object;
public fun deserializeAvro (Lcom/github/avrokotlin/avro4k/AvroDecoder;)Lorg/apache/avro/generic/GenericArray;
public fun getSchema (Lcom/github/avrokotlin/avro4k/serializer/SchemaSupplierContext;)Lorg/apache/avro/Schema;
public synthetic fun serializeAvro (Lcom/github/avrokotlin/avro4k/AvroEncoder;Ljava/lang/Object;)V
public fun serializeAvro (Lcom/github/avrokotlin/avro4k/AvroEncoder;Lorg/apache/avro/generic/GenericArray;)V
}

public final class com/github/avrokotlin/avro4k/serializer/GenericDataSerializer : com/github/avrokotlin/avro4k/serializer/AvroSerializer {
public static final field INSTANCE Lcom/github/avrokotlin/avro4k/serializer/GenericDataSerializer;
public fun deserializeAvro (Lcom/github/avrokotlin/avro4k/AvroDecoder;)Ljava/lang/Object;
public fun getSchema (Lcom/github/avrokotlin/avro4k/serializer/SchemaSupplierContext;)Lorg/apache/avro/Schema;
public fun serializeAvro (Lcom/github/avrokotlin/avro4k/AvroEncoder;Ljava/lang/Object;)V
}

public final class com/github/avrokotlin/avro4k/serializer/GenericDataSerializersKt {
public static final fun getGenericDataSerializersModule ()Lkotlinx/serialization/modules/SerializersModule;
}

public final class com/github/avrokotlin/avro4k/serializer/GenericEnumSerializer : com/github/avrokotlin/avro4k/serializer/AvroSerializer {
public static final field INSTANCE Lcom/github/avrokotlin/avro4k/serializer/GenericEnumSerializer;
public synthetic fun deserializeAvro (Lcom/github/avrokotlin/avro4k/AvroDecoder;)Ljava/lang/Object;
public fun deserializeAvro (Lcom/github/avrokotlin/avro4k/AvroDecoder;)Lorg/apache/avro/generic/GenericEnumSymbol;
public fun getSchema (Lcom/github/avrokotlin/avro4k/serializer/SchemaSupplierContext;)Lorg/apache/avro/Schema;
public synthetic fun serializeAvro (Lcom/github/avrokotlin/avro4k/AvroEncoder;Ljava/lang/Object;)V
public fun serializeAvro (Lcom/github/avrokotlin/avro4k/AvroEncoder;Lorg/apache/avro/generic/GenericEnumSymbol;)V
}

public final class com/github/avrokotlin/avro4k/serializer/GenericFixedSerializer : com/github/avrokotlin/avro4k/serializer/AvroSerializer {
public static final field INSTANCE Lcom/github/avrokotlin/avro4k/serializer/GenericFixedSerializer;
public synthetic fun deserializeAvro (Lcom/github/avrokotlin/avro4k/AvroDecoder;)Ljava/lang/Object;
public fun deserializeAvro (Lcom/github/avrokotlin/avro4k/AvroDecoder;)Lorg/apache/avro/generic/GenericFixed;
public fun getSchema (Lcom/github/avrokotlin/avro4k/serializer/SchemaSupplierContext;)Lorg/apache/avro/Schema;
public synthetic fun serializeAvro (Lcom/github/avrokotlin/avro4k/AvroEncoder;Ljava/lang/Object;)V
public fun serializeAvro (Lcom/github/avrokotlin/avro4k/AvroEncoder;Lorg/apache/avro/generic/GenericFixed;)V
}

public final class com/github/avrokotlin/avro4k/serializer/GenericRecordSerializer : com/github/avrokotlin/avro4k/serializer/AvroSerializer {
public static final field INSTANCE Lcom/github/avrokotlin/avro4k/serializer/GenericRecordSerializer;
public synthetic fun deserializeAvro (Lcom/github/avrokotlin/avro4k/AvroDecoder;)Ljava/lang/Object;
public fun deserializeAvro (Lcom/github/avrokotlin/avro4k/AvroDecoder;)Lorg/apache/avro/generic/GenericRecord;
public fun getSchema (Lcom/github/avrokotlin/avro4k/serializer/SchemaSupplierContext;)Lorg/apache/avro/Schema;
public synthetic fun serializeAvro (Lcom/github/avrokotlin/avro4k/AvroEncoder;Ljava/lang/Object;)V
public fun serializeAvro (Lcom/github/avrokotlin/avro4k/AvroEncoder;Lorg/apache/avro/generic/GenericRecord;)V
}

public final class com/github/avrokotlin/avro4k/serializer/InstantSerializer : com/github/avrokotlin/avro4k/serializer/AvroSerializer {
public static final field INSTANCE Lcom/github/avrokotlin/avro4k/serializer/InstantSerializer;
public synthetic fun deserializeAvro (Lcom/github/avrokotlin/avro4k/AvroDecoder;)Ljava/lang/Object;
Expand Down
11 changes: 9 additions & 2 deletions src/main/kotlin/com/github/avrokotlin/avro4k/Avro.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.github.avrokotlin.avro4k

import com.github.avrokotlin.avro4k.internal.EnumResolver
import com.github.avrokotlin.avro4k.internal.LogicalTypesSerializersCollector
import com.github.avrokotlin.avro4k.internal.PolymorphicResolver
import com.github.avrokotlin.avro4k.internal.RecordResolver
import com.github.avrokotlin.avro4k.internal.schema.ValueVisitor
import com.github.avrokotlin.avro4k.serializer.GenericDataSerializersModule
import com.github.avrokotlin.avro4k.serializer.JavaStdLibSerializersModule
import com.github.avrokotlin.avro4k.serializer.JavaTimeSerializersModule
import kotlinx.serialization.BinaryFormat
Expand All @@ -28,7 +30,7 @@ import java.io.ByteArrayInputStream
*/
public sealed class Avro(
public val configuration: AvroConfiguration,
public override val serializersModule: SerializersModule,
public final override val serializersModule: SerializersModule,
) : BinaryFormat {
// We use the identity hash map because we could have multiple descriptors with the same name, especially
// when having 2 different version of the schema for the same name. kotlinx-serialization is instantiating the descriptors
Expand All @@ -38,11 +40,16 @@ public sealed class Avro(
internal val recordResolver = RecordResolver(this)
internal val polymorphicResolver = PolymorphicResolver(serializersModule)
internal val enumResolver = EnumResolver()
internal val logicalTypeSerializers: Map<String, KSerializer<Any>> =
LogicalTypesSerializersCollector(configuration)
.apply { serializersModule.dumpTo(this) }
.serializers

public companion object Default : Avro(
AvroConfiguration(),
JavaStdLibSerializersModule +
JavaTimeSerializersModule
JavaTimeSerializersModule +
GenericDataSerializersModule
)

public fun schema(descriptor: SerialDescriptor): Schema {
Expand Down
2 changes: 2 additions & 0 deletions src/main/kotlin/com/github/avrokotlin/avro4k/AvroDecoder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ public interface AvroDecoder : Decoder {
@ExperimentalSerializationApi
public val currentWriterSchema: Schema

public val avro: Avro

/**
* Decode a [Schema.Type.BYTES] value.
*
Expand Down
2 changes: 2 additions & 0 deletions src/main/kotlin/com/github/avrokotlin/avro4k/AvroEncoder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ public interface AvroEncoder : Encoder {
@ExperimentalSerializationApi
public val currentWriterSchema: Schema

public val avro: Avro

/**
* Encodes a [Schema.Type.BYTES] value from a [ByteBuffer].
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.github.avrokotlin.avro4k.internal

import com.github.avrokotlin.avro4k.AvroConfiguration
import com.github.avrokotlin.avro4k.serializer.AvroSchemaSupplier
import com.github.avrokotlin.avro4k.serializer.ElementLocation
import com.github.avrokotlin.avro4k.serializer.SchemaSupplierContext
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.modules.SerializersModuleCollector
import kotlin.reflect.KClass

internal class LogicalTypesSerializersCollector(
private val configuration: AvroConfiguration,
) : SerializersModuleCollector {
internal var serializers: MutableMap<String, KSerializer<Any>> = mutableMapOf()

override fun <T : Any> contextual(
kClass: KClass<T>,
serializer: KSerializer<T>,
) {
if (serializer !is AvroSchemaSupplier || serializer.descriptor.isNullable) {
return
}
val schema = runCatching { serializer.getSchema(SimpleContext(configuration)) }.getOrNull()
if (schema?.logicalType != null) {
@Suppress("UNCHECKED_CAST")
serializers[schema.logicalType.name] = serializer as KSerializer<Any>
}
}

override fun <T : Any> contextual(
kClass: KClass<T>,
provider: (typeArgumentsSerializers: List<KSerializer<*>>) -> KSerializer<*>,
) {
}

override fun <Base : Any, Sub : Base> polymorphic(
baseClass: KClass<Base>,
actualClass: KClass<Sub>,
actualSerializer: KSerializer<Sub>,
) {
}

override fun <Base : Any> polymorphicDefaultDeserializer(
baseClass: KClass<Base>,
defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<Base>?,
) {
}

override fun <Base : Any> polymorphicDefaultSerializer(
baseClass: KClass<Base>,
defaultSerializerProvider: (value: Base) -> SerializationStrategy<Base>?,
) {
}
}

private class SimpleContext(
override val configuration: AvroConfiguration,
override val inlinedElements: List<ElementLocation> = emptyList(),
) : SchemaSupplierContext
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import org.apache.avro.generic.GenericData
import org.apache.avro.generic.GenericFixed

internal abstract class AbstractAvroDirectDecoder(
protected val avro: Avro,
override val avro: Avro,
protected val binaryDecoder: org.apache.avro.io.Decoder,
) : AbstractInterceptingDecoder(), UnionDecoder {
abstract override var currentWriterSchema: Schema
Expand Down Expand Up @@ -333,20 +333,27 @@ internal abstract class AbstractAvroDirectDecoder(
"string",
Schema.Type.STRING,
Schema.Type.BYTES,
Schema.Type.FIXED
Schema.Type.FIXED,
Schema.Type.ENUM
)
}) {
when (it.type) {
Schema.Type.STRING,
Schema.Type.BYTES,
-> {
Schema.Type.STRING -> {
AnyValueDecoder { binaryDecoder.readString() }
}

Schema.Type.BYTES -> {
AnyValueDecoder { binaryDecoder.readBytes(null).array().decodeToString() }
}

Schema.Type.FIXED -> {
AnyValueDecoder { ByteArray(it.fixedSize).also { buf -> binaryDecoder.readFixed(buf) }.decodeToString() }
}

Schema.Type.ENUM -> {
AnyValueDecoder { it.enumSymbols[binaryDecoder.readEnum()] }
}

else -> null
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.github.avrokotlin.avro4k.internal.decoder.generic

import com.github.avrokotlin.avro4k.Avro
import com.github.avrokotlin.avro4k.AvroDecoder
import com.github.avrokotlin.avro4k.internal.BadDecodedValueError
import com.github.avrokotlin.avro4k.internal.SerializerLocatorMiddleware
Expand Down Expand Up @@ -29,8 +28,6 @@ import java.math.BigDecimal
import java.nio.ByteBuffer

internal abstract class AbstractAvroGenericDecoder : AbstractDecoder(), AvroDecoder {
internal abstract val avro: Avro

abstract override fun decodeNotNullMark(): Boolean

abstract override fun decodeValue(): Any
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ internal class AvroValueDirectEncoder(
) : AbstractAvroDirectEncoder(avro, binaryEncoder)

internal sealed class AbstractAvroDirectEncoder(
protected val avro: Avro,
override val avro: Avro,
protected val binaryEncoder: org.apache.avro.io.Encoder,
) : AbstractEncoder(), AvroEncoder, UnionEncoder {
private var selectedUnionIndex: Int = -1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ private class RecordSequentialDirectEncoder(
private class RecordBadOrderDirectEncoder(
private val classDescriptor: ClassDescriptorForWriterSchema,
private val schema: Schema,
private val avro: Avro,
override val avro: Avro,
private val binaryEncoder: org.apache.avro.io.Encoder,
) : AbstractEncoder(), AvroEncoder {
// Each time we encode a field, if the next expected schema field index is not the good one, it is buffered until it's the time to encode it
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.github.avrokotlin.avro4k.internal.encoder.generic

import com.github.avrokotlin.avro4k.Avro
import com.github.avrokotlin.avro4k.AvroEncoder
import com.github.avrokotlin.avro4k.UnionEncoder
import com.github.avrokotlin.avro4k.encodeResolving
Expand All @@ -22,8 +21,6 @@ import org.apache.avro.generic.GenericFixed
import java.nio.ByteBuffer

internal abstract class AbstractAvroGenericEncoder : AbstractEncoder(), AvroEncoder, UnionEncoder {
abstract val avro: Avro

abstract override var currentWriterSchema: Schema

abstract override fun encodeValue(value: Any)
Expand Down
Loading