Closed Mitkee closed 7 months ago
Hello! Thank you for reporting this problem. I will investigate the solution. Best, Adrian
Hello @Mitkee
The fix is ready. From v.3.4.2 of AvroConvert the get-only properties should not be throwing errors during deserialization.
Regards, Adrian
Awesome, thanks! 🙏
While the basic cases seem to work, it seems certain chains of interactions are causing deserialization to fail still.
using NUnit.Framework;
using SolTechnology.Avro;
namespace TestNestedReadonly;
public record Parent
{
public List<Child> Children { get; set; } = new();
}
public record Child
{
public List<Toy> Toys { get; set; } = new();
//Need to clean this up
public int? TotalPrice => Toys != null && Toys.Any() ? Toys.Sum(v => v.Price) : null;
public int? TotalWeight => Toys!= null && Toys.Any() ? Toys.Sum(v => v.Weight) : null;
// Uncomment this for an interesting process crash
//public decimal? AveragePricePerWeight => Toys?.Average(t => t.PricePerWeightUnit);
}
public record Toy
{
public string Name { get; set; }
public int Weight { get; set; }
public int Price { get; set; }
public decimal PricePerWeightUnit => decimal.Round(Price / Weight, 2);
}
public class AvroReadonlyIssue
{
[Test]
public void TestReadonlySerializationAndDeserialization()
{
var parent = new Parent()
{
Children = new List<Child>
{
new Child()
{
Toys = new List<Toy>()
{
new Toy()
{
Weight = 10, Price = 11, Name = "Toy1"
},
new Toy()
{
Weight = 6, Price = 2, Name = "Toy2"
}
},
}
}
};
var serialized = AvroConvert.Serialize(parent);
var deserialized = AvroConvert.Deserialize<Parent>(serialized);
Assert.Pass();
}
}
Take the above test - the model is serialized properly, but deserializing gives
SolTechnology.Avro.Infrastructure.Exceptions.AvroTypeMismatchException : Unable to deserialize [Parent] of schema [Record] to the target type [TestNes...
SolTechnology.Avro.Infrastructure.Exceptions.AvroTypeMismatchException : Unable to deserialize [Parent] of schema [Record] to the target type [TestNestedReadonly.Parent]. Inner exception:
----> SolTechnology.Avro.Infrastructure.Exceptions.AvroTypeMismatchException : Unable to deserialize [array] of schema [Array] to the target type [System.Collections.Generic.List`1[TestNestedReadonly.Child]]. Inner exception:
----> SolTechnology.Avro.Infrastructure.Exceptions.AvroTypeMismatchException : Unable to deserialize [Child] of schema [Record] to the target type [TestNestedReadonly.Child]. Inner exception:
----> SolTechnology.Avro.Infrastructure.Exceptions.AvroTypeMismatchException : Unable to deserialize [array] of schema [Array] to the target type [System.Collections.Generic.List`1[TestNestedReadonly.Toy]]. Inner exception:
----> SolTechnology.Avro.Infrastructure.Exceptions.AvroTypeMismatchException : Unable to deserialize [Toy] of schema [Record] to the target type [TestNestedReadonly.Toy]. Inner exception:
----> SolTechnology.Avro.Infrastructure.Exceptions.AvroTypeMismatchException : Unable to deserialize [int] of schema [Int] to the target type [System.Int32]. Inner exception:
----> System.IO.EndOfStreamException : Attempted to read past the end of the stream.
at SolTechnology.Avro.AvroObjectServices.Read.Resolver.Resolve(TypeSchema writerSchema, TypeSchema readerSchema, IReader reader, Type type)
at SolTechnology.Avro.AvroObjectServices.Read.Resolver.Resolve[T](IReader reader, Int64 itemsCount)
at SolTechnology.Avro.Features.Deserialize.Decoder.Read[T](Reader reader, Header header, AbstractCodec codec, Resolver resolver)
at SolTechnology.Avro.Features.Deserialize.Decoder.Decode[T](Stream stream, TypeSchema readSchema)
at SolTechnology.Avro.AvroConvert.Deserialize[T](Byte[] avroBytes)
at TestNestedReadonly.AvroReadonlyIssue.TestReadonlySerializationAndDeserialization() in C:\Projects\nps.dataservices.auctions\NPS.DataServices.Auction.IntegrationTests\TestRepositories\AvroReadonlyIssue.cs:line 63
--AvroTypeMismatchException
at SolTechnology.Avro.AvroObjectServices.Read.Resolver.Resolve(TypeSchema writerSchema, TypeSchema readerSchema, IReader reader, Type type)
at SolTechnology.Avro.AvroObjectServices.Read.Resolver.ResolveRecord(RecordSchema writerSchema, RecordSchema readerSchema, IReader reader, Type type)
at SolTechnology.Avro.AvroObjectServices.Read.Resolver.Resolve(TypeSchema writerSchema, TypeSchema readerSchema, IReader reader, Type type)
--AvroTypeMismatchException
at SolTechnology.Avro.AvroObjectServices.Read.Resolver.Resolve(TypeSchema writerSchema, TypeSchema readerSchema, IReader reader, Type type)
at SolTechnology.Avro.AvroObjectServices.Read.Resolver.ResolveArray(TypeSchema writerSchema, TypeSchema readerSchema, IReader reader, Type type, Int64 itemsCount)
at SolTechnology.Avro.AvroObjectServices.Read.Resolver.Resolve(TypeSchema writerSchema, TypeSchema readerSchema, IReader reader, Type type)
--AvroTypeMismatchException
at SolTechnology.Avro.AvroObjectServices.Read.Resolver.Resolve(TypeSchema writerSchema, TypeSchema readerSchema, IReader reader, Type type)
at SolTechnology.Avro.AvroObjectServices.Read.Resolver.ResolveRecord(RecordSchema writerSchema, RecordSchema readerSchema, IReader reader, Type type)
at SolTechnology.Avro.AvroObjectServices.Read.Resolver.Resolve(TypeSchema writerSchema, TypeSchema readerSchema, IReader reader, Type type)
--AvroTypeMismatchException
at SolTechnology.Avro.AvroObjectServices.Read.Resolver.Resolve(TypeSchema writerSchema, TypeSchema readerSchema, IReader reader, Type type)
at SolTechnology.Avro.AvroObjectServices.Read.Resolver.ResolveArray(TypeSchema writerSchema, TypeSchema readerSchema, IReader reader, Type type, Int64 itemsCount)
at SolTechnology.Avro.AvroObjectServices.Read.Resolver.Resolve(TypeSchema writerSchema, TypeSchema readerSchema, IReader reader, Type type)
--AvroTypeMismatchException
at SolTechnology.Avro.AvroObjectServices.Read.Resolver.Resolve(TypeSchema writerSchema, TypeSchema readerSchema, IReader reader, Type type)
at SolTechnology.Avro.AvroObjectServices.Read.Resolver.ResolveRecord(RecordSchema writerSchema, RecordSchema readerSchema, IReader reader, Type type)
at SolTechnology.Avro.AvroObjectServices.Read.Resolver.Resolve(TypeSchema writerSchema, TypeSchema readerSchema, IReader reader, Type type)
--EndOfStreamException
at SolTechnology.Avro.AvroObjectServices.Read.Reader.Read()
at SolTechnology.Avro.AvroObjectServices.Read.Reader.ReadLong()
at SolTechnology.Avro.AvroObjectServices.Read.Reader.ReadInt()
at SolTechnology.Avro.AvroObjectServices.Read.Resolver.ResolveInt(Type readType, IReader reader)
at SolTechnology.Avro.AvroObjectServices.Read.Resolver.Resolve(TypeSchema writerSchema, TypeSchema readerSchema, IReader reader, Type type)
-----
One or more child tests had errors
Exception doesn't have a stacktrace
Even more interesting - uncommenting //public decimal? AveragePricePerWeight => Toys?.Average(t => t.PricePerWeightUnit);
causes the whole process to fail when deserializing. It doesn't throw out, but instead this happens:
I've a feeling something weird is still happening with the read only properties here? In basic cases, it works as expected.
Well, I need to dig deeper into that. Thank you for providing the test case
Hey,
The problem was with caching during the deserialization of a collection of classes with read-only properties. It should be fixed now. As for the calculated decimal property you might want to add AvroDecimal attribute:
[AvroDecimal(Precision = 28, Scale = 28)]
public decimal? AveragePricePerWeight => Toys?.Average(t => t.PricePerWeightUnit);
It's to ensure the result not exceeding max precision of avro decimal.
Best, Adrian
Describe the solution you'd like Currently, get only properties is not supported in deserialization. For example:
We can correctly write this schema, but we'll be unable to read it since NextNumber does not have a setter. As a workaround for this, the following can be done:
It would be nice if get only properties would be ignored either out of the box, or if a special attribute could be added to opt in for this behavior.
Describe implementation idea How it could be done? Either we make the following case work out of the box;
Or, a custom attribute is added to support this case:
The expected outcome of both options would be that the value is written when deserializing, but it won't be populated when deserializing.