[CSHARP-4223] InvalidCastException for getting Serializer for a list/array of structs Created: 21/Jun/22  Updated: 27/Oct/23  Resolved: 11/Jul/22

Status: Closed
Project: C# Driver
Component/s: Serialization
Affects Version/s: 2.15.1
Fix Version/s: None

Type: Bug Priority: Unknown
Reporter: Christian Zorneck Assignee: Robert Stam
Resolution: Gone away Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified


 Description   

Summary

I try to serialize/deserialize list/arrays of structs. I already implemented my own serializer/deserializer for structs. But lists or arrays of structs break already before serializing, when the BsonSerializerRegistry trys to get the serializer:

 

InvalidCastException: Specified cast is not valid.
MongoDB.Bson.Serialization.BsonSerializerRegistry.GetSerializer[T] () (at <8c9bb23734cb40cca67bc4a37718db2d>:0)
MongoDB.Bson.Serialization.Serializers.EnumerableSerializerBase`2+<>c__DisplayClass4_0[TValue,TItem].<.ctor>b__0 () (at <8c9bb23734cb40cca67bc4a37718db2d>:0) 

 

If I don't define any custom serializer, the serialization works somehow fine, but than the deserialization do not work.

How to Reproduce

public class MyDataClass {
  public object Id;
  public MyStruct[] Ms = MyStruct[2];
}
 
public struct MyStruct {
  public string SomeData;
}
 
...
MyDataClass data = new MyDataClass();
BsonSerializer.Serialize(new MongoDB.Bson.IO.BsonDocumentWriter(new MongoDB.Bson.BsonDocument()), typeof(MyDataClass), data);
...

Additional Background

We write a part of framework for persisting game data to mongodb. Because the users of this framework likes to use structs for their data, because of the performance and so on, we have to support structs and also list/arrays of structs.



 Comments   
Comment by PM Bot [ 11/Jul/22 ]

There hasn't been any recent activity on this ticket, so we're resolving it. Thanks for reaching out! Please feel free to comment on this if you're able to provide more information.

Comment by Robert Stam [ 23/Jun/22 ]

All serializers must implement `IBsonSerializer<T>`. It is not sufficient to only implement `IBsonSerializer`.

The non-generic `IBsonSerializer` interface is only intended to be used to pass around serializers when `<T>` is not known at compile time, but the actual serializer must still implement `IBsonSerializer<T>`.

Even if a custom serializer implementing only `IBsonSerializer` appears to work in limited scenarios, that is accidental, not by design.

 

Comment by Christian Zorneck [ 22/Jun/22 ]

@Robert thank you for your fast investigation! After days of trying and yesterday opening this issue, I finally found the problem.

I used the non generic interface for my serializer:

public class StructSerializer : IBsonSerializer {
...
} 

...but you have to use the generic variant of this interface, to work with lists:

public class StructSerializer<T> : IBsonSerializer<T> {
...
}

Without using this, it works fine if the field or property is of the type of the struct, but throws the InvalidCastException, if it is part of a list or array. Than the driver trys to cast it to the generic interface, which do not work.

// code from the driver
public IBsonSerializer<T> GetSerializer<T>() {
    return (IBsonSerializer<T>)GetSerializer(typeof(T));
} 

I don't know if this should/can be handled in some way, that it do not throw this exception, and cast and use it without generic type, like it is doing if it is not part of list/array.

Comment by Robert Stam [ 21/Jun/22 ]

I am unable to reproduce this.

I used the following test to attempt to reproduce. I had to fill in some missing pieces to get a a test that would compile and run. You could compare the pieces I wrote to your own code to see if I did anything different than you.

public class CSharp4223Tests
{
    [Fact]
    public void Test()
    {
        var ms = new[] { new MyStruct { SomeData = "abc" }, new MyStruct { SomeData = "def" } };
        var data = new MyDataClass
        {
            Id = 1,
            Ms = ms
        };
 
        var serialized = new BsonDocument();
        var writer = new BsonDocumentWriter(serialized);
        BsonSerializer.Serialize(writer, typeof(MyDataClass), data);
 
        var reader = new BsonDocument(serialized);
        var deserialized = BsonSerializer.Deserialize<MyDataClass>(reader);
 
        deserialized.Id.Should().Be(1);
        deserialized.Ms.Should().Equal(ms);
    }
 
    public class MyDataClass
    {
        public int Id;
        public MyStruct[] Ms;
    }
 
    [BsonSerializer(typeof(MyStructSerializer))]
    public struct MyStruct
    {
        public string SomeData;
    }
 
    public class MyStructSerializer : StructSerializerBase<MyStruct>
    {
        public override MyStruct Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
        {
            var reader = context.Reader;
 
            reader.ReadStartDocument();
            reader.ReadName("SomeData");
            var someData = reader.ReadString();
            reader.ReadEndDocument();
 
            return new MyStruct { SomeData = someData };
        }
 
        public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, MyStruct value)
        {
            var writer = context.Writer;
 
            writer.WriteStartDocument();
            writer.WriteName("SomeData");
            writer.WriteString(value.SomeData);
            writer.WriteEndDocument();
        }
    }
}

Generated at Wed Feb 07 21:47:36 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.