Search code examples
c#mongodbmongodb-.net-driver

C# MongoDB complex class serialization


I'm working with C# MongoDB driver and I have this rather complex JSON struct to save:

{
    "name" : "value",
    "age": 1,
    "isFemale": true,
    "Hobbies" : {
        //All data within the "Hobbies" node is dynamic
        //and may change from one item to another.
        "stringItem" : "value",
        "intItem" : 0.0,
        "listOfItems" : [
            { "field" : 1696.0 }
        ],
        "intArray" : [ 566.0,  1200.0 ]
    },
    "Collection" : [ 
        //All data within the "Collection" node is dynamic
        //and may change from one item to another.
        {
            "field" : "string",
            "FieldTypeId" : 2.0,
            "array" : [ 
                { "value" : "1024x1000" }
            ]
        }
    ]
}

I have this class that represents the above object:

public class MyClass
{
    public string Name;
    public int Age;
    public bool IsFemale;
    public Dictionary<string, object> Hobbies;
    public List<Dictionary<string, object>> Collection;
}

Here is what I get saved for the "Hobbies" section:

"Hobbies": {
    "stringItem": "value",
    "intItem": 1,
    "listOfItems": {
        "_t": "Newtonsoft.Json.Linq.JArray, Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed",
        "_v": [{
            "_t": "JObject",
            "_v": [{
                "_t": "JProperty",
                "_v": [{
                    "_t": "JValue",
                    "_v": []
                }]
            },
            {
                "_t": "JProperty",
                "_v": [{
                    "_t": "JValue",
                    "_v": []
                }]
            }]
        }]
    }
}

My guess is that I should write a custom serializer for that class but I couldn't find any useful example in the MONGODB .NET DRIVER Manual

I tried to start and implement something like this:

public class MyClassSerializer : SerializerBase<MyClass>, IBsonDocumentSerializer
{
    public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, MyClass value)
    {
        //What to do here???
        base.Serialize(context, args, value);
    }

    public override MyClass Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
    {
        //What to do here???
        return base.Deserialize(context, args);
    }

    public bool TryGetMemberSerializationInfo(string memberName, out BsonSerializationInfo serializationInfo)
    {
        switch (memberName)
        {
            case "Name":
                serializationInfo = new BsonSerializationInfo(memberName, new StringSerializer(), typeof(string));
                return true;
            case "Age":
                serializationInfo = new BsonSerializationInfo(memberName, new Int16Serializer(), typeof(string));
                return true;
            case "IsFemale":
                serializationInfo = new BsonSerializationInfo(memberName, new BooleanSerializer(), typeof(string));
                return true;
            case "Hobbies":
                serializationInfo = new BsonSerializationInfo(memberName, new TupleSerializer<string, object>(), typeof(string));
                return true;
            case "Collection":
                serializationInfo = new BsonSerializationInfo(memberName, new ArraySerializer<Dictionary<string, object>>(), typeof(string));
                return true;
            default:
                serializationInfo = null;
                return false;
        }
    }
}

But I couldn't find anything to do with this. It is not fully implemented and I read that I need to register the serializer somewhere, but where...?


Solution

  • Found how to save the data to MongoDB here: Dictionary-to-BsonDocument conversion omitting _t field and extended it a bit so I thought to share the full solution.

    Step #1:

    In my class, I declared 2 members for each value:

    // For the Hobbies object type:
    [BsonIgnore] //ignore this value in MongoDB
    public Dictionary<string, object> Hobbies { get; set; }
    
    [JsonIgnore] //ignore this value in the response on Get requests
    [BsonElement(elementName: "Hobbies")]
    public BsonDocument HobbiesBson { get; set; }
    
    /*********************************************************************/
    
    // For the Collection object type:
    [BsonIgnore] //ignore this value in MongoDB
    public List<Dictionary<string, object>> Collection { get; set; }
    
    [JsonIgnore] //ignore this value in the response on Get requests
    [BsonElement(elementName: "Collection")]
    public BsonArray CollectionBson { get; set; }
    

    Step #2

    In my WebAPI controller method for Post

    [HttpPost]
    public override async Task<IActionResult> Post([FromBody] Person person)
    {
        var jsonDoc = JsonConvert.SerializeObject(person.Hobbies);
        person.HobbiesBson = BsonSerializer.Deserialize<BsonDocument>(jsonDoc);
    
        jsonDoc = JsonConvert.SerializeObject(person.Collection);
        person.CollectionBson = BsonSerializer.Deserialize<BsonArray>(jsonDoc);
    
        //save
    }
    

    Step #3

    In my Get request I deserialize it back like this:

    [HttpGet("{id?}")]
    public override async Task<IActionResult> Get(string id = null)
    {
        var people = //get data from mongoDB
        foreach (var person in people)
        {
            var bsonDoc = BsonExtensionMethods.ToJson(person.HobbiesBson);
            person.Hobbies = JsonConvert.DeserializeObject<Dictionary<string, object>>(bsonDoc);
    
            bsonDoc = BsonExtensionMethods.ToJson(person.CollectionBson);
            person.Collection = JsonConvert.DeserializeObject<List<Dictionary<string, object>>>(bsonDoc);bsonDoc);
        }
        return Ok(people);
    }
    

    This solved my issue and I hope it can help others as well :-)