[CSHARP-3263] BsonSerializer fails to resolve custom serializer. StackOverflow exception when register derived serializers Created: 20/Nov/20  Updated: 27/Oct/23  Resolved: 12/Dec/20

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

Type: Bug Priority: Major - P3
Reporter: Eduard Muradov Assignee: James Kovacs
Resolution: Gone away Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified


 Description   

It looks like BsonSerializer fails to resolve proper serializer for a member when declared member type doesn't match actual initialized type.

Observed code flow (might not be exact):

  • BsonMemberMap looks up serializer and calls .Serialize() on found serializer (via extension method I believe).
  • Matched serializer of type ClassSerializerBase<T> calls Serialize() method on base class.
    Serialize() method looks up serializer on actual runtime type (e.g. value.GetType()) which fails to  find serializer for a concrete type.

If you register concrete type serializer as well, driver fails with StackOverflow exception because of infinite serializer lookup loop.

Please take a look at minimal repro below.

class Program
{
        public class Root
        {
            // Initialized type doesn't match declared type!!!
            public IDictionary<int, Node> Nodes { get; set; } 
               = new Dictionary<int, Node>();
        }        
 
        public class Node
        { 
            public string Name { get; set; }
        }        
 
        static void Main(string[] args)
        {
            BsonSerializer.RegisterSerializer(typeof(IDictionary<int, Node>),
                GetInt32Serializer<Node>());            
 
            var root = new Root();
            root.Nodes.Add(0, new Node { Name = "Test" }); 
 
            using (var stringWriter = new StringWriter())
            {
                using (var writer = new JsonWriter(stringWriter))
                {
                    BsonSerializer.Serialize(writer, root);                     
                    Console.WriteLine("Valid Root: {0}", stringWriter.ToString());
                }
            }            
            Console.ReadKey();
        }   
 
        private static IBsonSerializer GetInt32Serializer<TValue>()
        {
            return new DictionaryInterfaceImplementerSerializer<IDictionary<int, TValue>, int, TValue>(
                DictionaryRepresentation.Document,
                new Int32Serializer(BsonType.String),
                BsonSerializer.SerializerRegistry.GetSerializer<TValue>());
        }
    }



 Comments   
Comment by Backlog - Core Eng Program Management Team [ 12/Dec/20 ]

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 James Kovacs [ 27/Nov/20 ]

Hi, Eduard,

Thank you for the update and additional details. Please let us know if you can reliably reproduce the stack overflow as we would like to investigate this further. We look forward to reviewing any further examples that you can provide.

Sincerely,
James

Comment by Eduard Muradov [ 27/Nov/20 ]

Hi James, thank you for looking into this. 

Adding a serializer for a nominal type was a first thing I attempted but I got an error saying that its missing serializer for a declared type (IDictionary). Then, I tried adding both of the serializer registrations, for IDictionary and Dictionary, and the program started silently crashing; when I tried to debug it, it looked like infinite loop trying to resolve serializer ending up with SO exception at the end.

I will take a look at it in more detail over the weekend and will provide a better examples for this issue (the object I was getting this errors from is much more complicated than I showed above). Once again, thank you for looking into this.

 

 

Comment by James Kovacs [ 24/Nov/20 ]

Hi, Eduard,

Thank you for providing the repro. We understand that you are trying to serialize a child dictionary as a document using integer keys and it is failing with the following exception:

MongoDB.Bson.BsonSerializationException: When using DictionaryRepresentation.Document key values must serialize as strings

When the driver attempts to serialize the IDictionary<int, Node> property, it finds the registered serializer (with the new Int32Serializer(BsonType.String) for the key. Because the nominal type of the property (IDictionary<int, Node>) does not match the concrete type of the class that you are serializing, we perform a lookup for a Dictionary<int, Node> serializer, don't find one, and create a default one using new Int32Serializer(BsonType.Int32) for the key. This is why the driver fails with the above exception - because of the implicitly created concrete serializer.

In order to serialize and deserialize the dictionary, we need to know the concrete class - in this case Dictionary<int, Node>. So rather than registering the interface with the BsonSerializer register the concrete class instead.

BsonSerializer.RegisterSerializer(typeof(Dictionary<int, Node>), 
    new DictionaryInterfaceImplementerSerializer<Dictionary<int, Node>, int, Node>(
                DictionaryRepresentation.Document,
                new Int32Serializer(BsonType.String),
                BsonSerializer.SerializerRegistry.GetSerializer<Node>());

This will allow the driver to serialize the underlying concrete class Dictionary<int, Node> correctly. If you use other concrete classes such as SortedDictionary<K,V>, you would need to register those too.

For more complex scenario, you can register a custom serializer against the interface, which will give you complete control over the serialization/deserialization of any type implementing that interface.

Please let us know if you have any additional questions and whether registering serializers for the concrete type(s) resolves your issue.

ASIDE: We were unable to reproduce the stack overflow exception that you reported when registering both the interface and concrete types. If you are still encountering this issue, please provide a repro that throws a StackOverflowException so that we can investigate further.

Sincerely,
James

Comment by Eduard Muradov [ 20/Nov/20 ]

Thank you for a quick response!

Comment by Jeffrey Yemin [ 20/Nov/20 ]

Hi notyourhacker@gmail.com

Thanks for letting us know that you're experiencing this problem with serialization.  We'll have a look soon and get back to you with the results of our initial investigation.

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