Deserializing ExpandoObject with BsonSerializer in Mongo — Digitteck
Deserializing ExpandoObject with BsonSerializer in Mongo
dotnet·19 November 2019·2 min read

Deserializing ExpandoObject with BsonSerializer in Mongo

Some MongoDB aggregation stages return dynamic results — often arriving as an ExpandoObject when you project into a dynamic type. Deserializing that back into a typed class involves the same serializer the driver uses internally — just called explicitly. A look at the driver test source reveals the two-step process.

Target Model

The target class uses standard BSON annotations. Note the [BsonElement("LastName")] mapping — the deserialization respects attribute-based field renaming:

csharp
public class Courses
{
    public string Name { get; set; }
}

public class Person
{
    [BsonId(IdGenerator = typeof(GuidGenerator))]
    [BsonIgnoreIfDefault]
    public Guid UniqueId { get; set; }

    [BsonElement("LastName")]
    public string Name { get; set; }

    public string FirstName { get; set; }

    public List<int> Scores { get; set; }

    public Courses Courses { get; set; }
}

Solve — Two Lines That Do the Work

Step one: serialize the ExpandoObject to BSON bytes using a dynamic-type flag. Step two: deserialize those bytes into the target type using BsonSerializer with the ExpandoObject serializer as the dynamic document serializer:

csharp
// Two lines from the MongoDB C# driver source — serialize the ExpandoObject as BSON
// then deserialize with a dynamic document serializer so BsonSerializer can map it
public static TProjection Solve<TProjection>(ExpandoObject keyValuePairs)
    where TProjection : class
{
    // Serialize: tell the BSON encoder to treat ExpandoObject as a dynamic type
    var bson = keyValuePairs.ToBson(
        configurator: b => b.IsDynamicType = t => t == typeof(ExpandoObject));

    // Deserialize: provide an ExpandoObject serializer to handle nested dynamic objects
    var rehydrated = BsonSerializer.Deserialize<BsonDocument>(bson,
        b => b.DynamicDocumentSerializer = BsonSerializer.LookupSerializer<ExpandoObject>());

    return BsonSerializer.Deserialize<TProjection>(rehydrated);
}

Test

Build an ExpandoObject manually to verify the mapping works end-to-end — including nested objects and BSON field rename via attribute:

csharp
// Build a test ExpandoObject that mirrors the Person model
IDictionary<string, object> personExpando = new ExpandoObject();
personExpando.Add("LastName",   "Rami");
personExpando.Add("FirstName",  "John");
personExpando.Add("Scores",     new List<int> { 1, 2, 3 });

IDictionary<string, object> courses = new ExpandoObject();
courses.Add("Name", "Math");
personExpando.Add("Courses", courses);
personExpando.Add("_id", "b5c5cf38-e0c9-4b93-b8e5-b9c7f6c1839b");

Person person = Solve<Person>((ExpandoObject)personExpando);
// person.Name      → "Rami"    (BsonElement maps "LastName" → Name)
// person.FirstName → "John"
// person.Courses.Name → "Math"

Tags

.NETC#MongoDBSerialization
digitteck

© 2026 Digitteck