Skip to content

Commit

Permalink
feat: Add JsonElement support
Browse files Browse the repository at this point in the history
  • Loading branch information
Chuckame committed Feb 10, 2025
1 parent 0e5e62b commit d0b1177
Show file tree
Hide file tree
Showing 13 changed files with 1,041 additions and 300 deletions.
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

0 comments on commit d0b1177

Please sign in to comment.