[CSHARP-961] StackoverflowException deserializing Dictionary<string, Interface> Created: 25/Apr/14  Updated: 02/Apr/15  Resolved: 29/Apr/14

Status: Closed
Project: C# Driver
Component/s: Serialization
Affects Version/s: 1.8.3, 1.9
Fix Version/s: 1.9.1

Type: Bug Priority: Major - P3
Reporter: Andrew Martinez Assignee: Robert Stam
Resolution: Done Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified


 Description   

Experienced

When deserializing a class that has a property defines as Dictionary<string, Interface> a StackoverFlow exception will be thrown in mscorlib.dll.

If the property is changed to Dictionary<string, Concrete> the issue does not occur.

Expected

Interface deserialization on Dictionary properties where the value is an interface type should work.

Reproduction

Console Main

static void Main(string[] args)
{
 
    var client = new MongoClient();
    var server = client.GetServer();
    var db = server.GetDatabase("test");
    var collection = db.GetCollection("ParentChildren");
    BsonClassMap.RegisterClassMap<Parent>();
    BsonClassMap.RegisterClassMap<Child>();
 
    var parents = collection.FindAllAs<Parent>();
 
            
    foreach (var parent in parents)
    {
        Console.Out.WriteLine(parent.Name);
 
        foreach (var keyValuePair in parent.StringToChildren)
        {
            var child = keyValuePair.Value;
            Console.Out.WriteLine("Key: "+ keyValuePair.Key +" Name: " + child.Name);
        }
    }
 
    Console.Out.WriteLine("Press Enter to exit");
    Console.ReadLine();
}

Interfaces

public interface IParent
{
    [BsonId]
    BsonObjectId Id { get; set; }
 
    [BsonElement("name")]
    string Name { get; set; }
 
    [BsonElement("children")]
    Dictionary<string, IChild> StringToChildren { get; set; }
    //above is the issue, changing IChild to Child here and in the
    //concrete works around the issue.
}
 
 
public interface IChild
{
    [BsonElement("name")]
    string Name { get; set; }
}

Concretes

public class Parent : IParent
{
    [BsonId]
    public BsonObjectId Id { get; set; }
 
    [BsonElement("name")]
    public string Name { get; set; }
 
    [BsonElement("children")]
    public Dictionary<string, IChild> StringToChildren { get; set; }
}
 
public class Child: IChild
{
    [BsonElement("name")]
    public string Name { get; set; }
}

Example Document

{
    "_id" : ObjectId("535a524abfcae300b04cea7b"),
    "name" : "ParentName",
    "children" : {
        "one" : {
            "name" : "bob"
        },
        "two" : {
            "name" : "jane"
        }
    }
}



 Comments   
Comment by Andrew Martinez [ 25/Apr/14 ]

This exact example was created to represent my actual issue. I'm issuing a mapReduce query that returns a data structure that is created in the finalize execution. I am essentially creating a object mapping of types to counts. This object mapping does not actually exist on its own in any collection nor is it initially persisted from c#; it is just a product of the mapReduce. I was using the response object to mapReduceResponse.GetInlineResultsAs<Concrete>, where the concrete was very similar to the Parent concrete in my example (having a Dictionary<string, Interface> property).

If I understand the situation, my only option is to deserialize this myself; correct? Or is there another path that I can take?

Edit: The more I think about this the more I realize that my mapReduce output is pretty concrete. There can really never be other representations without altering the mapReduce logic which would in turn immediately require changes to the types. The only time I would need it is if I was persisting the data which would be covered by the discriminator (_t) attribute.

Anyways, at least I found that StackoverFlow exception.

Comment by Robert Stam [ 25/Apr/14 ]

We have resolved the StackOverflowException that occurred when _t was missing from the Child documents. Instead, there will be a FileFormatException with the following message:

Unable to determine actual type of object to deserialize. NominalType is the interface MongoDB.BsonUnitTests.Jira.CSharp961Tests+IChild.

(The actual interface name would vary slightly for you...).

But we are still unable to deserialize Child documents when the _t is missing. That's a somewhat bigger problem, namely "how would we determine which actual concrete class to use for an arbitrary interface if there was no _t to tell us that?".

Comment by Githook User [ 25/Apr/14 ]

Author:

{u'name': u'rstam', u'email': u'robert@10gen.com'}

Message: CSHARP-961: Add error message when deserializing a nominal type that is an interface and there is no discriminator to determine the actual type.
Branch: v1.x
https://github.com/mongodb/mongo-csharp-driver/commit/8ba324ec5dcc20af2981e556da1bc628886ffe62

Comment by Robert Stam [ 25/Apr/14 ]

If I save a Parent document to the database and read it back I don't get a StackOverflowException.

But if I try to read back your sample document I do.

The document that my test saved to the database differs slightly from your sample document, in that the Dictionary values have an _t discriminator value in them. The discriminator is used during deserialization to determine which concrete class to instantiate (you can't instantiate interfaces, only concrete classes can be instantiated). Here's what the Parent document looks like when I save a Parent to the database:

{ 
    '_id' : ObjectId('535a524abfcae300b04cea7b'),
    'name' : 'ParentName',
    'children' :
        { 'one' : { '_t' : 'Child', 'name' : 'bob' },
        'two' : { '_t' : 'Child', 'name' : 'jane' }
    }
}

When the _t is missing the driver ends up in a recursive code path that eventually results in a StackOverflowException.

What should happen is an error message saying that the _t discriminator is missing and that the driver is unable to determine which concrete class to use.

Comment by Robert Stam [ 25/Apr/14 ]

Thanks for reporting this. I will reproduce it and figure out what's happening.

Comment by Andrew Martinez [ 25/Apr/14 ]

Expected output:

ParentName
Key: one Name: bob
Key: two Name: jane
Press Enter to exit

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