fix-polymorphic-value-class-union-schema-name#479
fix-polymorphic-value-class-union-schema-name#479jantoebes wants to merge 5 commits intoavro-kotlin:mainfrom
Conversation
Value-class wrappers generated inside a sealed interface for a complex
Avro union had no @AvroAlias pointing at the inner Avro full name, so
PolymorphicResolver could not map the incoming schema fullName
(e.g. "com.example.MyRecord", "int", "map") to the wrapper
SerialName ("ForMyRecord", "ForInt", "ForMap") at runtime.
This caused SerializationException: 'Serializer for subclass ... is not
found' on decode for every multi-type union.
Fix: emit @AvroAlias(subSchema.fullName) on each value-class wrapper so
AbstractPolymorphicDecoder can resolve the subtype via the existing
alias lookup in PolymorphicResolver.
…e for value class union members For value/inline classes the Avro schema in a union is the unwrapped inner type (e.g. 'string' for a String-backed value class), not a named record. The resolver was building its lookup map with the value class serial name as key, which never matched the schema fullName during decode. Now the map key is derived from the produced Avro schema type, unwrapping inline classes recursively down to the primitive Avro type name.
| TypeSpec.classBuilder(unionSubTypeNameFormatter(if (hasSimilarNames) subSchema.fullName else subSchema.simpleName.toPascalCase())) | ||
| .addSuperinterface(ClassName.fromFullName(className)) | ||
| .addModifiers(KModifier.VALUE) | ||
| .addAnnotationIfNotNull(buildAvroAliasAnnotation(listOf(subSchema.fullName))) |
There was a problem hiding this comment.
Avro aliases are avro aliases, not for workaround. What are you trying to solve with this alias?
Because the polymorphic resolver should actually resolve this workaround
| .flatMap { | ||
| sequence { | ||
| yield(it.nonNullSerialName to it.nonNullSerialName) | ||
| yield(it.avroUnionSchemaFullName() to it.nonNullSerialName) |
There was a problem hiding this comment.
Here you are trying to resolve a descriptor to its schema name. The best is to pass a schemaNameResolver: (SerialDescriptor) -> String, and in the Avro class, pass { schema(it).name } as there are many things to do to resolve a name. Example: for Fixed type, it's a ByteArray with the @AvroFixed annotation. any type with @AvroString is finally a string. etc
…of manual primitive kind mapping
| @Serializable | ||
| @AvroGenerated("""["null",{"type":"record","name":"NestedRecord","fields":[{"name":"id","type":"string"},{"name":"value","type":"int"}]},{"type":"enum","name":"Status","symbols":["ACTIVE","INACTIVE","PENDING"]},{"type":"array","items":"string"}]""") | ||
| public sealed interface TheFieldUnion { | ||
| @AvroAlias("NestedRecord") |
There was a problem hiding this comment.
Don't forget to re-run actionsBeforeCommit
There was a problem hiding this comment.
ok done! thx for the review :-D
value class Label(val value: String)), the Avro union contains"string", not a named record. The resolver was building its map with the serial name as key, which never matchedschema.fullNameduring decode, causing aSerializationExceptionavroUnionSchemaFullName()which unwraps inline classes recursively to resolve the correct Avro primitive type name (e.g.PrimitiveKind.STRING → "string"); named types (records, enums) are unchangedCloses #480