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: Add JsonElement support #284

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
13 changes: 7 additions & 6 deletions api/avro4k-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -463,12 +463,13 @@ public final class com/github/avrokotlin/avro4k/serializer/JavaPeriodSerializer
public fun serializeGeneric (Lkotlinx/serialization/encoding/Encoder;Ljava/time/Period;)V
}

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

public final class com/github/avrokotlin/avro4k/serializer/JavaTimeSerializersKt {
public static final fun getJavaTimeSerializersModule ()Lkotlinx/serialization/modules/SerializersModule;
public final class com/github/avrokotlin/avro4k/serializer/JsonElementAvroSerializer : com/github/avrokotlin/avro4k/serializer/AvroSerializer {
public static final field INSTANCE Lcom/github/avrokotlin/avro4k/serializer/JsonElementAvroSerializer;
public synthetic fun deserializeAvro (Lcom/github/avrokotlin/avro4k/AvroDecoder;)Ljava/lang/Object;
public fun deserializeAvro (Lcom/github/avrokotlin/avro4k/AvroDecoder;)Lkotlinx/serialization/json/JsonElement;
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;Lkotlinx/serialization/json/JsonElement;)V
}

public final class com/github/avrokotlin/avro4k/serializer/LocalDateSerializer : com/github/avrokotlin/avro4k/serializer/AvroSerializer {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@ internal class ApacheAvroReflectBenchmark : SerializationBenchmark() {
ReflectData.get().addLogicalTypeConversion(TimeConversions.DateConversion())
ReflectData.get().addLogicalTypeConversion(TimeConversions.TimestampMillisConversion())

@Suppress("UNCHECKED_CAST")
writer = ReflectData.get().createDatumWriter(schema) as DatumWriter<Clients>
encoder = EncoderFactory.get().directBinaryEncoder(OutputStream.nullOutputStream(), null)

@Suppress("UNCHECKED_CAST")
reader = ReflectData.get().createDatumReader(schema) as DatumReader<Clients>
}

Expand Down
4 changes: 3 additions & 1 deletion src/main/kotlin/com/github/avrokotlin/avro4k/Avro.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.github.avrokotlin.avro4k.internal.decodeWithApacheDecoder
import com.github.avrokotlin.avro4k.internal.schema.ValueVisitor
import com.github.avrokotlin.avro4k.serializer.JavaStdLibSerializersModule
import com.github.avrokotlin.avro4k.serializer.JavaTimeSerializersModule
import com.github.avrokotlin.avro4k.serializer.KotlinxJsonSerializersModule
import kotlinx.serialization.BinaryFormat
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.ExperimentalSerializationApi
Expand Down Expand Up @@ -43,7 +44,8 @@ public sealed class Avro(
public companion object Default : Avro(
AvroConfiguration(),
JavaStdLibSerializersModule +
JavaTimeSerializersModule
JavaTimeSerializersModule +
KotlinxJsonSerializersModule
)

public fun schema(descriptor: SerialDescriptor): Schema {
Expand Down
105 changes: 105 additions & 0 deletions src/main/kotlin/com/github/avrokotlin/avro4k/internal/Schemas.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package com.github.avrokotlin.avro4k.internal

import kotlinx.serialization.descriptors.SerialDescriptor
import org.apache.avro.LogicalType
import org.apache.avro.Schema

internal val Schema.nullable: Schema
get() {
if (isNullable) return this
return if (isUnion) {
Schema.createUnion(listOf(Schema.create(Schema.Type.NULL)) + this.types)
} else {
Schema.createUnion(Schema.create(Schema.Type.NULL), this)
}
}

internal fun Schema.asSchemaList(): List<Schema> {
if (!isUnion) return listOf(this)
return types
}

internal fun Schema.isNamedSchema(): Boolean {
return this.type == Schema.Type.RECORD || this.type == Schema.Type.ENUM || this.type == Schema.Type.FIXED
}

internal fun Schema.isFullNameOrAliasMatch(descriptor: SerialDescriptor): Boolean {
return isFullNameOrAliasMatch(descriptor.nonNullSerialName, descriptor::aliases)
}

internal fun Schema.isFullNameOrAliasMatch(
fullName: String,
aliases: () -> Set<String>,
): Boolean {
return isFullNameMatch(fullName) || aliases().any { isFullNameMatch(it) }
}

internal fun Schema.isFullNameMatch(fullNameToMatch: String): Boolean {
return fullName == fullNameToMatch ||
(type == Schema.Type.RECORD || type == Schema.Type.ENUM || type == Schema.Type.FIXED) &&
aliases.any { it == fullNameToMatch }
}

private val SCHEMA_PLACEHOLDER = Schema.create(Schema.Type.NULL)

internal fun Schema.copy(
name: String = this.name,
doc: String? = this.doc,
namespace: String? = if (this.type == Schema.Type.RECORD || this.type == Schema.Type.ENUM || this.type == Schema.Type.FIXED) this.namespace else null,
aliases: Set<String> = if (this.type == Schema.Type.RECORD || this.type == Schema.Type.ENUM || this.type == Schema.Type.FIXED) this.aliases else emptySet(),
isError: Boolean = if (this.type == Schema.Type.RECORD) this.isError else false,
types: List<Schema> = if (this.isUnion) this.types.toList() else emptyList(),
enumSymbols: List<String> = if (this.type == Schema.Type.ENUM) this.enumSymbols.toList() else emptyList(),
fields: List<Schema.Field>? = if (this.type == Schema.Type.RECORD && this.hasFields()) this.fields.map { it.copy() } else null,
enumDefault: String? = if (this.type == Schema.Type.ENUM) this.enumDefault else null,
fixedSize: Int = if (this.type == Schema.Type.FIXED) this.fixedSize else -1,
valueType: Schema = if (this.type == Schema.Type.MAP) this.valueType else SCHEMA_PLACEHOLDER,
elementType: Schema = if (this.type == Schema.Type.ARRAY) this.elementType else SCHEMA_PLACEHOLDER,
objectProps: Map<String, Any> = this.objectProps,
additionalProps: Map<String, Any> = emptyMap(),
logicalType: LogicalType? = this.logicalType,
): Schema {
@Suppress("WHEN_ENUM_CAN_BE_NULL_IN_JAVA")
return when (type) {
Schema.Type.RECORD -> if (hasFields()) Schema.createRecord(name, doc, namespace, isError, fields) else Schema.createRecord(name, doc, namespace, isError)
Schema.Type.ENUM -> Schema.createEnum(name, doc, namespace, enumSymbols, enumDefault)
Schema.Type.FIXED -> Schema.createFixed(name, doc, namespace, fixedSize)

Schema.Type.UNION -> Schema.createUnion(types)
Schema.Type.MAP -> Schema.createMap(valueType)
Schema.Type.ARRAY -> Schema.createArray(elementType)
Schema.Type.BYTES,
Schema.Type.STRING,
Schema.Type.INT,
Schema.Type.LONG,
Schema.Type.FLOAT,
Schema.Type.DOUBLE,
Schema.Type.BOOLEAN,
Schema.Type.NULL,
-> Schema.create(type)
}
.also { newSchema ->
objectProps.forEach { (key, value) -> newSchema.addProp(key, value) }
additionalProps.forEach { (key, value) -> newSchema.addProp(key, value) }
logicalType?.addToSchema(newSchema)
aliases.forEach { newSchema.addAlias(it) }
}
}

internal fun Schema.Field.copy(
name: String = this.name(),
schema: Schema = this.schema(),
doc: String? = this.doc(),
defaultVal: Any? = this.defaultVal(),
order: Schema.Field.Order = this.order(),
aliases: Set<String> = this.aliases(),
objectProps: Map<String, Any> = this.objectProps,
additionalProps: Map<String, Any> = emptyMap(),
): Schema.Field {
return Schema.Field(name, schema, doc, defaultVal, order)
.also { newSchema ->
objectProps.forEach { (key, value) -> newSchema.addProp(key, value) }
additionalProps.forEach { (key, value) -> newSchema.addProp(key, value) }
aliases.forEach { newSchema.addAlias(it) }
}
}
Loading
Loading