[
https://issues.apache.org/jira/browse/AVRO-4318?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Kenneth Ry Ulrik updated AVRO-4318:
-----------------------------------
Description:
Currently, if a field in an Avro record defines an Avro Union of types, the
resulting C# class property type becomes "object", meaning any reference type,
which leaves users of the generated class to manually type cast into associated
generated classes "on the side" of the root/primary class, with no compiler
help to validate whether those casts are valid.
*Sample schema:*
{code:java}
{
"name": "RecordWithAvroUnionField",
"namespace": "example",
"type": "record",
"fields": [
{
"name": "avroUnionField",
"type": [
{
"name": "AvroUnionTypeA",
"type": "record",
"fields": [
{
"name": "myString",
"type": "string"
}
]
},
{
"name": "AvroUnionTypeB",
"type": "record",
"fields": [
{
"name": "myInt",
"type": "int"
}
]
}
]
}
]
}{code}
*What gets generated today:*
* RecordWithAvroUnionField.cs
* AvroUnionTypeA.cs
* AvroUnionTypeB.cs
+What's wrong with it?+
* The "avroUnionField" field is generated as a property with type "object"
* Despite RecordWithAvroUnionField literally containing and declaring the
types AvroUnionTypeA and AvroUnionTypeB, there is no type system reference,
direct or otherwise, to them at all, so it's up to consumers to "guess" /
explicitly cast into types that they must in advance know can be in this
"object"-typed property.
+Excerpt of generated "avroUnionField" C# property:+
(generates "object" property, requiring manual "guessing" / manual type-casting
to explicitly known types )
{code:java}
public object avroUnionField
{
get
{
return this._avroUnionField;
}
set
{
this._avroUnionField = value;
}
}{code}
+Excerpt of generated "AvroUnionTypeA" type as an example:+
(has no type system correlation to RecordWithAvroUnionField in any way)
{code:java}
public partial class AvroUnionTypeA : global::Avro.Specific.ISpecificRecord
{ ... } {code}
*The proposal for what to generate instead:*
* A marker (empty) interface, distinctly defined and named according to the
type and field name
* For each type declared as part of an Avro union, add the marker interface
onto the generated class.
* Modify Avro Union fields to use their distinctly defined marker interface as
the property type instead of object
+Marker interface naming proposal:+
(ensures uniqueness, avoiding rare scenarios where multiple nested types
declared in Avro Unions may declare a field with the same name, both of which
declare an Avro Union)
Format: $"\{NameOfTypeContainingAvroUnionField}_\{AvroUnionFieldName}"
{code:java}
public interface IRecordWithAvroUnionField_avroUnionField { }{code}
+"AvroUnionTypeA" type example:+
(implements the marker interface, because it is declared as part of this
particular Avro Union)
{code:java}
public partial class AvroUnionTypeA : global::Avro.Specific.ISpecificRecord,
IRecordWithAvroUnionField_avroUnionField
{ ... } {code}
+"avroUnionField" C# property in the "RecordWithAvroUnionField" class:+
(uses the marker interface as the type of the Avro Union field)
{code:java}
public IRecordWithAvroUnionField_avroUnionField avroUnionField
{
get
{
return this._avroUnionField;
}
set
{
this._avroUnionField = value;
}
}{code}
*Future note for C# union types:*
With C# 15 and .NET 11, with the introduction of union types, one could instead
generate a union type definition, with each class generated for the Avro Union
types being declared as part of that union type, allowing the C# compiler to
further aid developers by ensuring complete handling of all union member-type
possibilities in code that deserializes to the generated class.
[Explore union types in C# 15 - .NET
Blog|https://devblogs.microsoft.com/dotnet/csharp-15-union-types/]
I.e.:
{code:java}
public union RecordWithAvroUnionField_avroUnionField(AvroUnionTypeA,
AvroUnionTypeB); {code}
was:
Currently, if a field in an Avro record defines an Avro Union of types, the
resulting C# class property type becomes "object", leaving
*Sample schema:*
{code:java}
{
"name": "RecordWithAvroUnionField",
"namespace": "example",
"type": "record",
"fields": [
{
"name": "avroUnionField",
"type": [
{
"name": "AvroUnionTypeA",
"type": "record",
"fields": [
{
"name": "myString",
"type": "string"
}
]
},
{
"name": "AvroUnionTypeB",
"type": "record",
"fields": [
{
"name": "myInt",
"type": "int"
}
]
}
]
}
]
}{code}
*What gets generated today:*
* RecordWithAvroUnionField.cs
* AvroUnionTypeA.cs
* AvroUnionTypeB.cs
+What's wrong with it?+
* The "avroUnionField" field is generated as a property with type "object"
* Despite RecordWithAvroUnionField literally containing and declaring the
types AvroUnionTypeA and AvroUnionTypeB, there is no type system reference,
direct or otherwise, to them at all, so it's up to consumers to "guess" /
explicitly cast into types that they must in advance know can be in this
"object"-typed property.
+Excerpt of generated "avroUnionField" C# property:+
(generates "object" property, requiring manual "guessing" / manual type-casting
to explicitly known types )
{code:java}
public object avroUnionField
{
get
{
return this._avroUnionField;
}
set
{
this._avroUnionField = value;
}
}{code}
+Excerpt of generated "AvroUnionTypeA" type as an example:+
(has no type system correlation to RecordWithAvroUnionField in any way)
{code:java}
public partial class AvroUnionTypeA : global::Avro.Specific.ISpecificRecord
{ ... } {code}
*The proposal for what to generate instead:*
* A marker (empty) interface, distinctly defined and named according to the
type and field name
* For each type declared as part of an Avro union, add the marker interface
onto the generated class.
* Modify Avro Union fields to use their distinctly defined marker interface as
the property type instead of object
+Marker interface naming proposal:+
(ensures uniqueness, avoiding rare scenarios where multiple nested types
declared in Avro Unions may declare a field with the same name, both of which
declare an Avro Union)
Format: $"\{NameOfTypeContainingAvroUnionField}_\{AvroUnionFieldName}"
{code:java}
public interface IRecordWithAvroUnionField_avroUnionField { }{code}
+"AvroUnionTypeA" type example:+
(implements the marker interface, because it is declared as part of this
particular Avro Union)
{code:java}
public partial class AvroUnionTypeA : global::Avro.Specific.ISpecificRecord,
IRecordWithAvroUnionField_avroUnionField
{ ... } {code}
+"avroUnionField" C# property in the "RecordWithAvroUnionField" class:+
(uses the marker interface as the type of the Avro Union field)
{code:java}
public IRecordWithAvroUnionField_avroUnionField avroUnionField
{
get
{
return this._avroUnionField;
}
set
{
this._avroUnionField = value;
}
}{code}
*Future note for C# union types:*
With C# 15 and .NET 11, with the introduction of union types, one could instead
generate a union type definition, with each class generated for the Avro Union
types being declared as part of that union type, allowing the C# compiler to
further aid developers by ensuring complete handling of all union member-type
possibilities in code that deserializes to the generated class.
[Explore union types in C# 15 - .NET
Blog|https://devblogs.microsoft.com/dotnet/csharp-15-union-types/]
I.e.:
{code:java}
public union RecordWithAvroUnionField_avroUnionField(AvroUnionTypeA,
AvroUnionTypeB); {code}
> C# AvroGen: Marker Interface for Avro Unions
> --------------------------------------------
>
> Key: AVRO-4318
> URL: https://issues.apache.org/jira/browse/AVRO-4318
> Project: Apache Avro
> Issue Type: Improvement
> Components: csharp
> Affects Versions: 1.12.1
> Reporter: Kenneth Ry Ulrik
> Priority: Major
>
> Currently, if a field in an Avro record defines an Avro Union of types, the
> resulting C# class property type becomes "object", meaning any reference
> type, which leaves users of the generated class to manually type cast into
> associated generated classes "on the side" of the root/primary class, with no
> compiler help to validate whether those casts are valid.
> *Sample schema:*
>
> {code:java}
> {
> "name": "RecordWithAvroUnionField",
> "namespace": "example",
> "type": "record",
> "fields": [
> {
> "name": "avroUnionField",
> "type": [
> {
> "name": "AvroUnionTypeA",
> "type": "record",
> "fields": [
> {
> "name": "myString",
> "type": "string"
> }
> ]
> },
> {
> "name": "AvroUnionTypeB",
> "type": "record",
> "fields": [
> {
> "name": "myInt",
> "type": "int"
> }
> ]
> }
> ]
> }
> ]
> }{code}
> *What gets generated today:*
> * RecordWithAvroUnionField.cs
> * AvroUnionTypeA.cs
> * AvroUnionTypeB.cs
> +What's wrong with it?+
> * The "avroUnionField" field is generated as a property with type "object"
> * Despite RecordWithAvroUnionField literally containing and declaring the
> types AvroUnionTypeA and AvroUnionTypeB, there is no type system reference,
> direct or otherwise, to them at all, so it's up to consumers to "guess" /
> explicitly cast into types that they must in advance know can be in this
> "object"-typed property.
> +Excerpt of generated "avroUnionField" C# property:+
> (generates "object" property, requiring manual "guessing" / manual
> type-casting to explicitly known types )
>
> {code:java}
> public object avroUnionField
> {
> get
> {
> return this._avroUnionField;
> }
> set
> {
> this._avroUnionField = value;
> }
> }{code}
> +Excerpt of generated "AvroUnionTypeA" type as an example:+
> (has no type system correlation to RecordWithAvroUnionField in any way)
>
> {code:java}
> public partial class AvroUnionTypeA : global::Avro.Specific.ISpecificRecord
> { ... } {code}
>
>
> *The proposal for what to generate instead:*
> * A marker (empty) interface, distinctly defined and named according to the
> type and field name
> * For each type declared as part of an Avro union, add the marker interface
> onto the generated class.
> * Modify Avro Union fields to use their distinctly defined marker interface
> as the property type instead of object
> +Marker interface naming proposal:+
> (ensures uniqueness, avoiding rare scenarios where multiple nested types
> declared in Avro Unions may declare a field with the same name, both of which
> declare an Avro Union)
> Format: $"\{NameOfTypeContainingAvroUnionField}_\{AvroUnionFieldName}"
>
> {code:java}
> public interface IRecordWithAvroUnionField_avroUnionField { }{code}
>
> +"AvroUnionTypeA" type example:+
> (implements the marker interface, because it is declared as part of this
> particular Avro Union)
>
> {code:java}
> public partial class AvroUnionTypeA : global::Avro.Specific.ISpecificRecord,
> IRecordWithAvroUnionField_avroUnionField
> { ... } {code}
>
> +"avroUnionField" C# property in the "RecordWithAvroUnionField" class:+
> (uses the marker interface as the type of the Avro Union field)
>
> {code:java}
> public IRecordWithAvroUnionField_avroUnionField avroUnionField
> {
> get
> {
> return this._avroUnionField;
> }
> set
> {
> this._avroUnionField = value;
> }
> }{code}
>
>
> *Future note for C# union types:*
> With C# 15 and .NET 11, with the introduction of union types, one could
> instead generate a union type definition, with each class generated for the
> Avro Union types being declared as part of that union type, allowing the C#
> compiler to further aid developers by ensuring complete handling of all union
> member-type possibilities in code that deserializes to the generated class.
> [Explore union types in C# 15 - .NET
> Blog|https://devblogs.microsoft.com/dotnet/csharp-15-union-types/]
> I.e.:
> {code:java}
> public union RecordWithAvroUnionField_avroUnionField(AvroUnionTypeA,
> AvroUnionTypeB); {code}
>
>
--
This message was sent by Atlassian Jira
(v8.20.10#820010)