How to create custom serializer for values in dictionary?

Viewed 179

I have the following class:

 public class Farm
 {
   public string County {get;set;}
   
   public Dictionary<string, object> FarmItems {get;set;}
 }

I need to implement a custom serializer/deserializer that, if it's a integer in the value, store it in the DB as a string and then deserialize it back to a integer

I've looked at the docs and they gave details on how to do it for single values but not for dictionaries https://mongodb.github.io/mongo-csharp-driver/2.12/reference/bson/serialization/

Also, the docs don't mention how to implement it, do I just add an attribute after creating my class like so:

[BsonSerializer(typeof(MyCustomSerializer))]
public Dictionary<string, object> FarmItems {get;set;}
1 Answers

here you go:

public class MyDictionarySerialzer : SerializerBase<Dictionary<string, object>>
{
    public override void Serialize(BsonSerializationContext ctx, BsonSerializationArgs args, Dictionary<string, object> dictionary)
    {
        if (dictionary == null)
        {
            ctx.Writer.WriteStartArray();
            ctx.Writer.WriteEndArray();
            return;
        }

        ctx.Writer.WriteStartArray();

        foreach (var kvPair in dictionary)
        {
            ctx.Writer.WriteStartDocument();

            ctx.Writer.WriteName("k");
            ctx.Writer.WriteString(kvPair.Key);

            ctx.Writer.WriteName("v");
            if (kvPair.Value is int)
                ctx.Writer.WriteString(kvPair.Value.ToString());
            else
                BsonSerializer.Serialize(ctx.Writer, kvPair.Value);

            ctx.Writer.WriteEndDocument();
        }

        ctx.Writer.WriteEndArray();
    }

    public override Dictionary<string, object> Deserialize(BsonDeserializationContext ctx, BsonDeserializationArgs args)
    {
        Dictionary<string, object> dict = new();

        switch (ctx.Reader.CurrentBsonType)
        {
            case BsonType.Array:
                foreach (var val in BsonSerializer.Deserialize<BsonArray>(ctx.Reader))
                {
                    string key = val["k"].AsString;
                    object value = BsonSerializer.Deserialize<object>(val["v"].ToJson());

                    if (int.TryParse(value?.ToString(), out var parsedInt))
                        value = parsedInt;

                    dict.Add(key, value);
                }
                return dict;

            default:
                throw new BsonSerializationException("Unable to deserialize dictionary!");
        }
    }
}

if you are storing any complex entities in the dictionary values, you have to register (on app startup) that complex type with mongo driver like so (or deserialization will fail):

BsonClassMap.RegisterClassMap<ComplexItem>();

simply decorate the property like so:

public class Farm
{
    [BsonSerializer(typeof(MyDictionarySerialzer))]
    public Dictionary<string, object> FarmItems { get; set; }
}

example entity:

var example = new Farm
{
    FarmItems = new Dictionary<string, object>()
    {
        {"empty",null },
        {"string","test string" },
        {"int",100 },
        {"complex",new ComplexItem{ Age = 10, Name = "test" } }
    }
};

it will be serialized to the db like so:

{
    "FarmItems" : [
        {
            "k" : "empty",
            "v" : null
        },
        {
            "k" : "string",
            "v" : "test string"
        },
        {
            "k" : "int",
            "v" : "100"
        },
        {
            "k" : "complex",
            "v" : {
                "_t" : "ComplexItem",
                "Name" : "test",
                "Age" : 10
            }
        }
    ]
}
Related