Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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...?

like image 424
Liran Friedman Avatar asked Nov 27 '17 12:11

Liran Friedman


People also ask

What C is used for?

C programming language is a machine-independent programming language that is mainly used to create many types of applications and operating systems such as Windows, and other complicated programs such as the Oracle database, Git, Python interpreter, and games and is considered a programming foundation in the process of ...

What is the full name of C?

In the real sense it has no meaning or full form. It was developed by Dennis Ritchie and Ken Thompson at AT&T bell Lab. First, they used to call it as B language then later they made some improvement into it and renamed it as C and its superscript as C++ which was invented by Dr.

What is C in C language?

What is C? C is a general-purpose programming language created by Dennis Ritchie at the Bell Laboratories in 1972. It is a very popular language, despite being old. C is strongly associated with UNIX, as it was developed to write the UNIX operating system.

Is C language easy?

C is a general-purpose language that most programmers learn before moving on to more complex languages. From Unix and Windows to Tic Tac Toe and Photoshop, several of the most commonly used applications today have been built on C. It is easy to learn because: A simple syntax with only 32 keywords.


2 Answers

A simple way for dealing with this issue and not have to create additional properties, is registering a custom serializer.

ComplexTypeSerializer.cs

namespace MyNamespace.MongoDB.Serializers
{
    public class ComplexTypeSerializer : SerializerBase<object>
    {
        public override object Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
        {
            var serializer = BsonSerializer.LookupSerializer(typeof(BsonDocument));
            var document = serializer.Deserialize(context, args);

            var bsonDocument = document.ToBsonDocument();

            var result = BsonExtensionMethods.ToJson(bsonDocument);
            return JsonConvert.DeserializeObject<IDictionary<string, object>>(result);
        }

        public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value)
        {
            var jsonDocument = JsonConvert.SerializeObject(value);
            var bsonDocument = BsonSerializer.Deserialize<BsonDocument>(jsonDocument);

            var serializer = BsonSerializer.LookupSerializer(typeof(BsonDocument));
            serializer.Serialize(context, bsonDocument.AsBsonValue);
        }
    }
}

And register it as per http://mongodb.github.io/mongo-csharp-driver/2.3/reference/bson/serialization/

BsonSerializer.RegisterSerializer(typeof(IDictionary<string, object>), new ComplexTypeSerializer());

or

BsonSerializer.RegisterSerializer(typeof(IList<IDictionary<string, object>>), new ComplexTypeSerializer());

The code below was tested with a simple IDictionary<string, object>, not sure if it would work with IList<IDictionary<string, object>>, however if it's not supported, you can create another custom serializer to support it.

like image 156
Alexz Avatar answered Sep 17 '22 13:09

Alexz


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 :-)

like image 26
Liran Friedman Avatar answered Sep 20 '22 13:09

Liran Friedman