You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@avro.apache.org by "Dave Kliczbor (Jira)" <ji...@apache.org> on 2024/04/04 12:51:00 UTC
[jira] [Created] (AVRO-3969) [C#] Serializing a Map/Dictionary using SpecificDefaultWriter and JsonEncoder fails with AvroTypeException
Dave Kliczbor created AVRO-3969:
-----------------------------------
Summary: [C#] Serializing a Map/Dictionary using SpecificDefaultWriter and JsonEncoder fails with AvroTypeException
Key: AVRO-3969
URL: https://issues.apache.org/jira/browse/AVRO-3969
Project: Apache Avro
Issue Type: Bug
Components: csharp
Affects Versions: 1.11.3
Environment: We believe the environment to not matter here, but as there is a box for that here... :)
* Windows 11
* .NET 8.0
* C# 12
* Apache.Avro 1.11.3 (released via nuget)
Reporter: Dave Kliczbor
Hello,
We think we identified a bug in the SpecificDefaultWriter implementation of the Apache.Avro C# library (as of Release 1.11.3, also main branch as of 2024-04-03).
h4. Reproduction of the problem:
The following C# code throws an {{{}AvroTypeException("Attempt to process an array-start when a map-start was expected"){}}}:
{code:java}
var schema = MapSchema.CreateMap(PrimitiveSchema.Create(Schema.Type.String));
var ms = new MemoryStream();
Encoder enc = new JsonEncoder(schema, ms, false);
var writer = new SpecificDefaultWriter(schema);
writer.Write(new Dictionary<string, string>(), enc);
enc.Flush();{code}
We expect this code to not throw up.
The same code with {{BinaryEncoder(schema, ms)}} instead of {{JsonEncoder(schema, ms, false)}} works as expected: we get a Avro message byte array without an Exception being thrown.
h4. Preliminary Solution Analysis:
We believe the root cause to be in this 13 years old code:
[https://github.com/apache/avro/blob/8cb32f05d50382e5a5e97d6d45b080bef0c0e6a5/lang/csharp/src/apache/main/Specific/SpecificWriter.cs#L152]
{code:java}
protected override void WriteMap(MapSchema schema, object value, Encoder encoder)
{
var map = value as System.Collections.IDictionary;
if (map == null)
throw new AvroTypeException("Map does not implement non-generic IDictionary");
encoder.WriteArrayStart(); // <----- This is L.152
encoder.SetItemCount(map.Count);
foreach (System.Collections.DictionaryEntry de in map)
{
encoder.StartItem();
encoder.WriteString(de.Key as string);
Write(schema.ValueSchema, de.Value, encoder);
}
encoder.WriteMapEnd();
} {code}
In method {{{}SpecificDefaultWriter.WriteMap(MapSchema, object, Encoder){}}}, the call {{encoder.WriteArrayStart()}} is used. Which is in stark contrast to the usage of {{encoder.WriteMapEnd()}} later and also matches the Exception message above (using the BinaryEncoder works without problems, as {{WriteArrayStart}} and {{WriteMapStart }}are identical empty methods there).
Therefore, we believe that {{encoder.WriteMapStart();}} is the intended call in L.152.
h4. Additional information:
Here’s our test case (code using xUnit/FluentAssertions and Apache.Avro 1.11.3) that fails with the bug present:
{code:java}
[Fact]
public void AvroLibraryJsonEncoderDoesNotChokeOnMaps()
{
Action action = () =>
{
var schema = MapSchema.CreateMap(PrimitiveSchema.Create(Schema.Type.String));
var ms = new MemoryStream();
Encoder enc = new JsonEncoder(schema, ms, false);
var writer = new SpecificDefaultWriter(schema);
writer.Write(new Dictionary<string, string>(), enc);
enc.Flush();
};
action.Should().NotThrow<AvroTypeException>();
}{code}
Our workaround is to use this derived SpecificDefaultWriter class instead of the original from the Apache.Avro C# library. This works for our purposes, but up to now, we are mere users of the library and do not know yet whether this might break something else.
{code:java}
public class SpecificDefaultWriterWithWriteMapToJsonBugfix(Schema schema) : SpecificDefaultWriter(schema) {
protected override void WriteMap(MapSchema schema, object value, Encoder encoder)
{
if (!(value is IDictionary dictionary))
throw new AvroTypeException("Map does not implement non-generic IDictionary");
encoder.WriteMapStart();
encoder.SetItemCount((long)dictionary.Count);
foreach (DictionaryEntry dictionaryEntry in dictionary)
{
encoder.StartItem();
encoder.WriteString(dictionaryEntry.Key as string);
this.Write(schema.ValueSchema, dictionaryEntry.Value, encoder);
}
encoder.WriteMapEnd();
}
}
{code}
Cheers!
Dave Kliczbor (Materna SE)
--
This message was sent by Atlassian Jira
(v8.20.10#820010)