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

Release v2 #240

Merged
merged 107 commits into from
Jul 17, 2024
Merged
Changes from 1 commit
Commits
Show all changes
107 commits
Select commit Hold shift + click to select a range
b61ff5f
fix: Assume kotlin.Pair as a normal data class instead of an union
Chuckame Jan 21, 2024
79a132e
Merge pull request #174 from Chuckame/fix/kotlin-pair
Chuckame Jan 27, 2024
6a839b2
feat!: No more reflection and customizable logical types
Chuckame Jan 21, 2024
17eaf60
Merge pull request #175 from Chuckame/reflectionless
Chuckame Jan 27, 2024
dc27771
Merge pull request #177 from avro-kotlin/main
Chuckame Jan 27, 2024
c8492de
Generalize encoding/decoding tests (#168)
thake Jan 28, 2024
7696fe1
Merge pull request #179 from Chuckame/test/generalize-tests
Chuckame Jan 28, 2024
a485a3c
chore(build): Add spotless with ktlint + editorconfig
Chuckame Jan 28, 2024
4d59d4d
chore(spotless): Apply spotless
Chuckame Jan 28, 2024
24c9f2b
Merge pull request #180 from Chuckame/chore/formatting
Chuckame Jan 28, 2024
563c20f
feat: Fully support value classes and remove @AvroInline
Chuckame Jan 28, 2024
91dea8e
feat: Handle contextual map keys #114
Chuckame Jan 28, 2024
f6b186e
remove useless InlineDecoder as it is natively handled now
Chuckame Jan 29, 2024
b6f93e3
@AvroDefault & @ScalePrecision no more available for classes, as it i…
Chuckame Feb 8, 2024
1574c53
added assertThat test utils
Chuckame Feb 9, 2024
ebf6cc1
fixed decoding for map nullable values
Chuckame Feb 9, 2024
98cc0ae
remove useless line
Chuckame Apr 10, 2024
fdb53fc
apply spotless
Chuckame Apr 10, 2024
3cf493a
Merge pull request #183 from Chuckame/feat/value-classes
Chuckame Apr 10, 2024
f85e658
feat: Separate naming strategies (#178), remove AvroName[space] and a…
Chuckame Jan 28, 2024
237a3a1
feat: Add naming cache
Chuckame Feb 3, 2024
f0ec3bb
Merge pull request #182 from Chuckame/feat/separated-naming-strategy
Chuckame Apr 11, 2024
8ce714f
feat: Merge ScalePrecision to AvroDecimalLogicalType
Chuckame Apr 11, 2024
9a126af
Merge pull request #191 from Chuckame/feat/merge-decimal-ann
Chuckame Apr 11, 2024
cb35a3b
chore: Upgrade github actions and use standard gradle actions
Chuckame Apr 11, 2024
8aa48f3
Merge pull request #192 from Chuckame/gh-actions-update
Chuckame Apr 11, 2024
ea31c5d
chore: Add kover
Chuckame Apr 21, 2024
9e7ab01
deps: Remove snappy library
Chuckame Apr 21, 2024
29cc5e1
feat: revamp the schema generation
Chuckame Apr 21, 2024
69a369a
remove namingStrategy cache as there is a schema cache
Chuckame Apr 15, 2024
eee8a81
Merge pull request #190 from Chuckame/descriptor-visitor
Chuckame Apr 22, 2024
e3fdf92
feat: Handle char type
Chuckame Apr 21, 2024
bddc501
feat: New Avro entrypoint
Chuckame Apr 22, 2024
b3bf051
chore: remove redondant null checks
Chuckame Apr 22, 2024
b27b66d
chore: rename RecordNamingStrategy to TypeNamingStrategy
Chuckame Apr 22, 2024
85dc5c5
Merge pull request #186 from Chuckame/avro-api
Chuckame Apr 22, 2024
a4223ac
feat: Support everything at root level
Chuckame May 3, 2024
a6b4817
Merge pull request #202 from Chuckame/anything-root
Chuckame May 3, 2024
a4af252
feat!: Set @AvroEnumDefault directly to the enum value instead of the…
Chuckame May 3, 2024
bb5b035
feat!: Merge AvroJsonProp to AvroProp #201
Chuckame May 3, 2024
091f36c
Merge pull request #203 from Chuckame/enum-default
Chuckame May 3, 2024
325bd3f
Merge pull request #204 from Chuckame/unify-avro-json-prop
Chuckame May 3, 2024
a6df754
build: Explicit API mode to prevent exposing internal stuff
Chuckame May 3, 2024
e66ed3e
Merge pull request #205 from Chuckame/explicit-api-mode
Chuckame May 3, 2024
a16ca0c
deps: Upgrade kotlinx-serialization and kotlin
Chuckame May 9, 2024
0ec51a5
perf: Improve encoding performances with inlined union resolving
Chuckame May 7, 2024
a0d4272
chore: Add benchmark module
Chuckame May 9, 2024
a84209c
Merge pull request #208 from Chuckame/union-perf-improvement
Chuckame May 9, 2024
f3b2243
Merge pull request #209 from Chuckame/deps
Chuckame May 9, 2024
31b7889
docs: Improve README.md
Chuckame May 9, 2024
a030681
chore!: Stop supporting Timestamp class
Chuckame May 9, 2024
7c07046
chore! Remove custom schema as it is not supported in the serialization
Chuckame May 9, 2024
f3964d0
chore!: Internalize AvroLogicalType annotation, waiting for the new k…
Chuckame May 9, 2024
0c0725c
Merge pull request #210 from Chuckame/docs
Chuckame May 9, 2024
c8437a5
add sealed class in benchmark
Chuckame May 10, 2024
e8993e4
test: Support record with different field positions
Chuckame May 21, 2024
2d919da
test: Added nullable test case for nullable logical types
Chuckame May 11, 2024
7afa6ad
perf: Stop using tagger encoder
Chuckame May 11, 2024
be56714
perf: Stop using tagger decoder
Chuckame May 11, 2024
0ccd5d5
Add ExperimentalSerializationApi marker
Chuckame May 21, 2024
34e6393
feat!: No more kotlin-reflect for logical types
Chuckame May 23, 2024
7c90993
upgrade github workflows
Chuckame May 21, 2024
9cef510
Add gradle build scan
Chuckame May 23, 2024
8a99c60
docs: Add generic encoding note
Chuckame May 23, 2024
41dfff3
Merge pull request #214 from Chuckame/no-reflection
Chuckame May 23, 2024
b7f3b6b
perf: Direct decoding & encoding
Chuckame May 28, 2024
31306c1
Merge pull request #215 from Chuckame/direct-encoding
Chuckame May 29, 2024
c36c443
feat: Allow generating a release on a non-main branch
Chuckame May 29, 2024
2040d25
Merge pull request #217 from Chuckame/fix-release
Chuckame May 29, 2024
d3bc54b
feat: Allow adding props to a given type using value classes
Chuckame May 30, 2024
afc5fff
Merge pull request #219 from Chuckame/fix/props-value-class-element
Chuckame May 30, 2024
8c7b221
deps: Use non-RC version of kotlinx-serialization
Chuckame Jun 20, 2024
4804a9a
Merge pull request #221 from Chuckame/deps/non-rc-kotlinx-serialization
Chuckame Jun 20, 2024
a88f5cc
deps: Upgrade plugins, trying to fix publication failing
Chuckame Jun 20, 2024
9df5c94
Merge pull request #222 from Chuckame/deps/upgrade-versions
Chuckame Jun 20, 2024
78737b4
fix: Removed AvroNamespaceOverride as it was not fully implemented
Chuckame Jun 25, 2024
b6e52fc
feat: Implement BinaryFormat
Chuckame Jun 25, 2024
87c58ac
Add migration guide
Chuckame Jun 25, 2024
25f3f38
Merge pull request #224 from Chuckame/docs
Chuckame Jun 25, 2024
e5f5137
benchmark: Added apache avro lib generic<->binary benchmark
Chuckame Jun 25, 2024
7793a06
little refactor
Chuckame Jun 25, 2024
948643e
unifying union checks
Chuckame Jun 25, 2024
23814d2
Merge pull request #225 from Chuckame/improve-benchmark
Chuckame Jun 25, 2024
933fabb
fix docs
Chuckame Jun 26, 2024
78cf46f
Create dependabot.yml
Chuckame Jul 1, 2024
71a8e3c
Merge pull request #227 from avro-kotlin/Dependabot
Chuckame Jul 1, 2024
83544bd
Merge pull request #226 from Chuckame/docs
Chuckame Jul 1, 2024
228fcd1
handle nullable bytearrays and add null values in benchmark
Chuckame Jul 5, 2024
976dee7
Merge pull request #228 from Chuckame/docs
Chuckame Jul 5, 2024
5df37fb
feat: Add duration logical type
Chuckame Jul 10, 2024
ad17bb3
Merge pull request #233 from Chuckame/feat/duration-logical-type
Chuckame Jul 10, 2024
83934fc
fix: Only handle ByteArrays as bytes, and collection of Byte as array…
Chuckame Jul 10, 2024
489b45a
Merge pull request #234 from Chuckame/fix/remove-bytes
Chuckame Jul 10, 2024
01ccbfc
fix: No more automatic padding for fixed type
Chuckame Jul 10, 2024
34c32bb
Merge pull request #235 from Chuckame/feat/no-padding
Chuckame Jul 10, 2024
76d0134
fix: Update docs
Chuckame Jul 10, 2024
143d6a9
Merge pull request #237 from Chuckame/docs
Chuckame Jul 11, 2024
837ea2f
feat: Add @AvroStringable
Chuckame Jul 10, 2024
b0aec20
Merge pull request #236 from Chuckame/feat/stringable
Chuckame Jul 11, 2024
eb37957
feat: Remove AvroDecimal defaults
Chuckame Jul 11, 2024
eda4375
Merge pull request #238 from Chuckame/feat/decimal-default
Chuckame Jul 11, 2024
999a90c
feat: Add implicitEmptyCollections configuration
Chuckame Jul 14, 2024
967f5ed
Merge pull request #239 from Chuckame/implicit-empty-collections
Chuckame Jul 15, 2024
6a8dd19
Merge branch 'main' into main-v2
Chuckame Jul 16, 2024
84e88f4
fix documentation
Chuckame Jul 16, 2024
b64158f
benchmark: add simple use case
Chuckame Jul 16, 2024
f617d83
spotless + apiDump
Chuckame Jul 17, 2024
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
Prev Previous commit
Next Next commit
feat: Add implicitEmptyCollections configuration
  • Loading branch information
Chuckame committed Jul 15, 2024
commit 999a90c9e7c9e0e173fc8ed6158799cbdc742c91
55 changes: 54 additions & 1 deletion Migrating-from-v1.md
Original file line number Diff line number Diff line change
@@ -25,10 +25,63 @@ data class TheDataClass(
)

// Now
// ... Nothing, as it is the default behavior!
data class TheDataClass(
// ... Nothing, as it is the default behavior!
val field: String?
)

// Or
val avro = Avro { implicitNulls = false }
data class TheDataClass(
@AvroDefault("null")
val field: String?
)
```

## Set a field default value to empty array

```kotlin
// Previously
data class TheDataClass(
@AvroDefault("[]")
val field: List<String>
)

// Now
data class TheDataClass(
// ... Nothing, as it is the default behavior!
val field: List<String>
)

// Or
val avro = Avro { implicitEmptyCollections = false }
data class TheDataClass(
@AvroDefault("[]")
val field: List<String>
)
```

## Set a field default value to empty map

```kotlin
// Previously
data class TheDataClass(
@AvroDefault("{}")
val field: Map<String, String>
)

// Now
data class TheDataClass(
// ... Nothing, as it is the default behavior!
val field: Map<String, String>
)

// Or
val avro = Avro { implicitEmptyCollections = false }
data class TheDataClass(
@AvroDefault("{}")
val field: Map<String, String>
)
```

## generic data serialization
58 changes: 38 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -15,8 +15,8 @@ Here are the main features:
- **Encode and decode** anything to and from binary format, and also in generic data :toolbox:
- **Generate schemas** based on your values and data classes :pencil:
- **Customize** the generated schemas and encoded data with annotations :construction_worker:
- **Fast** as it is reflection-less :rocket:
- **Simple API** to get started quickly, also with native support of `java.time`, `BigDecimal`, `BigInteger` and `UUID` classes :1st_place_medal:
- **Fast** as it is reflection-less :rocket: (check the benchmarks [here](benchmark/README.md#results))
- **Simple API** to get started quickly, also with native support of java standard classes like `UUID`, `BigDecimal`, `BigInteger` and `java.time` module :1st_place_medal:
- **Relaxed matching** for easy schema evolution as it natively [adapts compatible types](#types-matrix) :cyclone:

> [!WARNING]
@@ -325,6 +325,37 @@ yourAvroInstance.schema<Pizza>()

# Usage

## Customizing the configuration

By default, `Avro` is configured with the following behavior:
- `implicitNulls`: The nullable fields are considered null when decoding if the writer record's schema does not contain this field.
- `implicitEmptyCollections`: The non-nullable map and collection fields are considered empty when decoding if the writer record's schema does not contain this field.
- If `implicitNulls` is true, it takes precedence so the empty collections are set as null if the value is missing instead of an empty collection.
- `validateSerialization`: There is no validation of the schema when encoding or decoding data, which means that serializing using a custom serializer could lead to unexpected behavior. Be careful with your custom serializers. More details [in this section](#set-a-custom-schema).
- `fieldNamingStrategy`: The record's field naming strategy is using the original kotlin field name. To change it, [check this section](#changing-records-field-name).

So each time you call a method on the `Avro` object implicitely invoke the default configuration. Example:

```kotlin
Avro.encodeToByteArray(MyData("value"))
Avro.decodeFromByteArray(bytes)
Avro.schema<MyData>()
```

If you need to change the default behavior, you need to create your own instance of `Avro` with the wanted configuration:

```kotlin
val yourAvroInstance = Avro {
fieldNamingStrategy = FieldNamingStrategy.Builtins.SnakeCase
implicitNulls = false
implicitEmptyCollections = false
validateSerialization = true
}
yourAvroInstance.encodeToByteArray(MyData("value"))
yourAvroInstance.decodeFromByteArray(bytes)
yourAvroInstance.schema<MyData>()
```

## Types matrix

| Kotlin type | Generated schema type | Other compatible writer types | Compatible logical type | Note / Serializer class |
@@ -529,6 +560,7 @@ There is 3 built-ins strategies:
- `NoOp` (default): keeps the original kotlin field name
- `SnakeCase`: converts the original kotlin field name to snake_case with underscores before each uppercase letter
- `PascalCase`: upper-case the first letter of the original kotlin field name
- If you need more, please [file an issue](https://github.com/avro-kotlin/avro4k/issues/new/choose)

First, create your own instance of `Avro` with the wanted naming strategy:

@@ -556,9 +588,9 @@ val schema = myCustomizedAvroInstance.schema<MyData>() // {...,"fields":[{"name"
While reading avro binary data, you can miss a field (a kotlin field is present but not in the avro binary data), so Avro4k fails as it is not capable of constructing the kotlin
type without the missing field value.

> [!NOTE]
> By default, all nullable fields are optional as a `default: null` is automatically added to the schema ([check this section](#disable-implicit-default-null-for-nullable-fields)
> to opt out from this default behavior).
By default:
- nullable fields are optional and `default: null` is automatically added to the field ([check this section](#disable-implicit-default-null-for-nullable-fields) to opt out from this default behavior).
- nullable fields are optional and `default: null` is automatically added to the field ([check this section](#disable-implicit-default-null-for-nullable-fields) to opt out from this default behavior).

### @AvroDefault

@@ -593,7 +625,7 @@ data class MyData(
)
```

> This impacts only the deserialization of the field, and not the serialization or deserialization.
> This impacts only the deserialization of the field, and not the serialization or the schema generation.

## Add aliases

@@ -775,20 +807,6 @@ data class Foo(val a: String, @Transient val b: String = "default value")
> [!NOTE]
> This impacts the schema generation, the serialization and the deserialization.

## Disable implicit `default: null` for nullable fields

Avro4k makes by default your nullable fields optional (put `default: null` on all nullable fields if no other explicit default provided).
You can opt out this feature by setting `implicitNulls` to `false` in the `Avro` configuration:

```kotlin
Avro {
implicitNulls = false
}
```

> [!NOTE]
> This impacts the schema generation, the serialization and the deserialization.

## Force a field to be a `string` type

You can force a field (or the value class' property) to have its inferred schema as a `string` type by annotating it with `@AvroString`.
13 changes: 8 additions & 5 deletions api/avro4k-core.api
Original file line number Diff line number Diff line change
@@ -27,28 +27,31 @@ public synthetic class com/github/avrokotlin/avro4k/AvroAlias$Impl : com/github/
}

public final class com/github/avrokotlin/avro4k/AvroBuilder {
public final fun build ()Lcom/github/avrokotlin/avro4k/AvroConfiguration;
public final fun getFieldNamingStrategy ()Lcom/github/avrokotlin/avro4k/FieldNamingStrategy;
public final fun getImplicitEmptyCollections ()Z
public final fun getImplicitNulls ()Z
public final fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule;
public final fun getValidateSerialization ()Z
public final fun setFieldNamingStrategy (Lcom/github/avrokotlin/avro4k/FieldNamingStrategy;)V
public final fun setImplicitEmptyCollections (Z)V
public final fun setImplicitNulls (Z)V
public final fun setSerializersModule (Lkotlinx/serialization/modules/SerializersModule;)V
public final fun setValidateSerialization (Z)V
}

public final class com/github/avrokotlin/avro4k/AvroConfiguration {
public fun <init> ()V
public fun <init> (Lcom/github/avrokotlin/avro4k/FieldNamingStrategy;ZZ)V
public synthetic fun <init> (Lcom/github/avrokotlin/avro4k/FieldNamingStrategy;ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Lcom/github/avrokotlin/avro4k/FieldNamingStrategy;ZZZ)V
public synthetic fun <init> (Lcom/github/avrokotlin/avro4k/FieldNamingStrategy;ZZZILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Lcom/github/avrokotlin/avro4k/FieldNamingStrategy;
public final fun component2 ()Z
public final fun component3 ()Z
public final fun copy (Lcom/github/avrokotlin/avro4k/FieldNamingStrategy;ZZ)Lcom/github/avrokotlin/avro4k/AvroConfiguration;
public static synthetic fun copy$default (Lcom/github/avrokotlin/avro4k/AvroConfiguration;Lcom/github/avrokotlin/avro4k/FieldNamingStrategy;ZZILjava/lang/Object;)Lcom/github/avrokotlin/avro4k/AvroConfiguration;
public final fun component4 ()Z
public final fun copy (Lcom/github/avrokotlin/avro4k/FieldNamingStrategy;ZZZ)Lcom/github/avrokotlin/avro4k/AvroConfiguration;
public static synthetic fun copy$default (Lcom/github/avrokotlin/avro4k/AvroConfiguration;Lcom/github/avrokotlin/avro4k/FieldNamingStrategy;ZZZILjava/lang/Object;)Lcom/github/avrokotlin/avro4k/AvroConfiguration;
public fun equals (Ljava/lang/Object;)Z
public final fun getFieldNamingStrategy ()Lcom/github/avrokotlin/avro4k/FieldNamingStrategy;
public final fun getImplicitEmptyCollections ()Z
public final fun getImplicitNulls ()Z
public final fun getValidateSerialization ()Z
public fun hashCode ()I
18 changes: 9 additions & 9 deletions benchmark/README.md
Original file line number Diff line number Diff line change
@@ -25,15 +25,15 @@ Each benchmark is executed with the following configuration:
Computer: Macbook air M2

```
Benchmark Mode Cnt Score Error Units Relative Difference (%)
Avro4kBenchmark.read thrpt 5 21443.935 ± 2215.328 ops/s 0.00%
ApacheAvroReflectBenchmark.read thrpt 5 19803.543 ± 485.869 ops/s -7.64%
Avro4kGenericWithApacheAvroBenchmark.read thrpt 5 8836.787 ± 404.874 ops/s -58.79%

Avro4kBenchmark.write thrpt 5 50565.556 ± 849.344 ops/s 0.00%
ApacheAvroReflectBenchmark.write thrpt 5 46872.768 ± 2406.622 ops/s -7.30%
JacksonAvroBenchmark.write thrpt 5 32349.182 ± 10105.111 ops/s -36.01%
Avro4kGenericWithApacheAvroBenchmark.write thrpt 5 27471.887 ± 315.498 ops/s -45.67%
Benchmark Mode Cnt Score Error Units Relative Difference (%)
Avro4kBenchmark.read thrpt 5 22306.113 ± 208.516 ops/s 0.00%
ApacheAvroReflectBenchmark.read thrpt 5 21048.047 ± 3974.761 ops/s -5.65%
Avro4kGenericWithApacheAvroBenchmark.read thrpt 5 8366.754 ± 975.268 ops/s -62.49%

Avro4kBenchmark.write thrpt 5 54307.187 ± 789.593 ops/s 0.00%
ApacheAvroReflectBenchmark.write thrpt 5 48056.580 ± 2290.755 ops/s -11.52%
JacksonAvroBenchmark.write thrpt 5 36193.366 ± 1124.036 ops/s -33.34%
Avro4kGenericWithApacheAvroBenchmark.write thrpt 5 28268.377 ± 117.031 ops/s -47.96%
```

> [!WARNING]
6 changes: 5 additions & 1 deletion src/main/kotlin/com/github/avrokotlin/avro4k/Avro.kt
Original file line number Diff line number Diff line change
@@ -107,14 +107,18 @@ public class AvroBuilder internal constructor(avro: Avro) {
@ExperimentalSerializationApi
public var implicitNulls: Boolean = avro.configuration.implicitNulls

@ExperimentalSerializationApi
public var implicitEmptyCollections: Boolean = avro.configuration.implicitEmptyCollections

@ExperimentalSerializationApi
public var validateSerialization: Boolean = avro.configuration.validateSerialization
public var serializersModule: SerializersModule = EmptySerializersModule()

public fun build(): AvroConfiguration =
internal fun build(): AvroConfiguration =
AvroConfiguration(
fieldNamingStrategy = fieldNamingStrategy,
implicitNulls = implicitNulls,
implicitEmptyCollections = implicitEmptyCollections,
validateSerialization = validateSerialization
)
}
Original file line number Diff line number Diff line change
@@ -18,6 +18,15 @@ public data class AvroConfiguration(
*/
@ExperimentalSerializationApi
val implicitNulls: Boolean = true,
/**
* By default, set to `true`, the array & map fields that haven't any default value are set as an empty array or map if the value is missing. It also adds `"default": []` for arrays or `"default": {}` for maps to those fields when generating schema using avro4k.
*
* If `implicitNulls` is true, the empty collections are set as null if the value is missing.
*
* When set to `false`, during decoding, any missing content for an array or a map field without its empty default value is failing.
*/
@ExperimentalSerializationApi
val implicitEmptyCollections: Boolean = true,
/**
* **To be removed when binary support is stable.**
*
Original file line number Diff line number Diff line change
@@ -139,36 +139,46 @@ internal class RecordResolver(
if (visited) return@forEachIndexed

val readerDefaultAnnotation = classDescriptor.findElementAnnotation<AvroDefault>(elementIndex)
// TODO try to fallback on the default value of the writer schema field if no readerDefaultAnnotation
val readerField = readerSchema.fields[elementIndex]

decodingSteps +=
if (readerDefaultAnnotation != null) {
val elementSchema = readerSchema.fields[elementIndex].schema()
DecodingStep.GetDefaultValue(
elementIndex = elementIndex,
schema = elementSchema,
defaultValue = readerDefaultAnnotation.parseValueToGenericData(elementSchema)
schema = readerField.schema(),
defaultValue = readerDefaultAnnotation.parseValueToGenericData(readerField.schema())
)
} else if (classDescriptor.isElementOptional(elementIndex)) {
DecodingStep.IgnoreOptionalElement(elementIndex)
} else if (avro.configuration.implicitNulls &&
(
classDescriptor.getElementDescriptor(elementIndex).isNullable ||
classDescriptor.getElementDescriptor(elementIndex).isInline && classDescriptor.getElementDescriptor(elementIndex).getElementDescriptor(0).isNullable
)
) {
} else if (avro.configuration.implicitNulls && readerField.schema().isNullable) {
DecodingStep.GetDefaultValue(
elementIndex = elementIndex,
schema = NULL_SCHEMA,
schema = readerField.schema().asSchemaList().first { it.type === Schema.Type.NULL },
defaultValue = null
)
} else if (avro.configuration.implicitEmptyCollections && readerField.schema().isTypeOf(Schema.Type.ARRAY)) {
DecodingStep.GetDefaultValue(
elementIndex = elementIndex,
schema = readerField.schema().asSchemaList().first { it.type === Schema.Type.ARRAY },
defaultValue = emptyList<Any>()
)
} else if (avro.configuration.implicitEmptyCollections && readerField.schema().isTypeOf(Schema.Type.MAP)) {
DecodingStep.GetDefaultValue(
elementIndex = elementIndex,
schema = readerField.schema().asSchemaList().first { it.type === Schema.Type.MAP },
defaultValue = emptyMap<String, Any>()
)
} else {
DecodingStep.MissingElementValueFailure(elementIndex)
}
}
return decodingSteps.toTypedArray()
}

private fun Schema.isTypeOf(expectedType: Schema.Type): Boolean {
return asSchemaList().any { it.type === expectedType }
}

private fun computeEncodingSteps(
classDescriptor: SerialDescriptor,
writerSchema: Schema,
@@ -265,7 +275,9 @@ internal sealed interface DecodingStep {

/**
* The element is present in the class descriptor but not in the writer schema, so the default value is used.
* Also, if the [com.github.avrokotlin.avro4k.AvroConfiguration.implicitNulls] is enabled, the default value is `null`.
* Also:
* - if the [com.github.avrokotlin.avro4k.AvroConfiguration.implicitNulls] is enabled, the default value is `null`.
* - if the [com.github.avrokotlin.avro4k.AvroConfiguration.implicitEmptyCollections] is enabled, the default value is an empty array or map.
*/
data class GetDefaultValue(
override val elementIndex: Int,
@@ -404,6 +416,4 @@ private fun Schema.resolveUnion(
throw SerializationException("Union type does not contain one of ${expectedTypes.asList()}, unable to convert default value '$value' for schema $this")
}
return types[index]
}

private val NULL_SCHEMA = Schema.create(Schema.Type.NULL)
}
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@ package com.github.avrokotlin.avro4k.internal.decoder.direct
import com.github.avrokotlin.avro4k.Avro
import com.github.avrokotlin.avro4k.internal.DecodingStep
import com.github.avrokotlin.avro4k.internal.decoder.generic.AvroValueGenericDecoder
import com.github.avrokotlin.avro4k.internal.nonNullSerialName
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.SerializationException
import kotlinx.serialization.builtins.ByteArraySerializer
@@ -14,13 +15,13 @@ import org.apache.avro.generic.GenericFixed
import org.apache.avro.io.Decoder

internal class RecordDirectDecoder(
recordSchema: Schema,
private val writerRecordSchema: Schema,
descriptor: SerialDescriptor,
avro: Avro,
binaryDecoder: org.apache.avro.io.Decoder,
) : AbstractAvroDirectDecoder(avro, binaryDecoder) {
// from descriptor element index to schema field. The missing fields are at the end to decode the default values
private val classDescriptor = avro.recordResolver.resolveFields(recordSchema, descriptor)
private val classDescriptor = avro.recordResolver.resolveFields(writerRecordSchema, descriptor)
private lateinit var currentDecodingStep: DecodingStep.ValidatedDecodingStep
private var nextDecodingStepIndex = 0

@@ -40,7 +41,11 @@ internal class RecordDirectDecoder(

is DecodingStep.SkipWriterField -> binaryDecoder.skip(field.schema)
is DecodingStep.MissingElementValueFailure -> {
throw SerializationException("No writer schema field matching element index ${field.elementIndex} in descriptor $descriptor")
throw SerializationException(
"Reader field '${descriptor.nonNullSerialName}.${descriptor.getElementName(
field.elementIndex
)}' has no corresponding field in writer schema $writerRecordSchema"
)
}

is DecodingStep.DeserializeWriterField -> {
Loading