Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deserializing field when type is changed using MongoDb csharp driver

I am testing a number of scenarios with MongoDb to see how to recover from possible data issues.

I have classes (Addresses with collection of Address) with a zipcode property in Address which was originally cast as string. I saved out multiple records of Addresses and could retrieve them all fine. like this, var allAddresses = addresses.FindAllAs();

I changed the zip code property to int and saved some records. I then changed the zip code property back to string.

When I attempt to read the collection back I get an error deserializing, as expected. var allAddresses = addresses.FindAllAs();

My goal is to be able to override the deserialization so if an field deserialization error occurs I can choose to either ignore it or apply a default value.

I have tried a custom serializer, which is not working. Any suggestions would be appreciated.

public class MyCustomSerializer : BsonBaseSerializer
  {

    public override object Deserialize(BsonReader bsonReader, Type nominalType,  IBsonSerializationOptions options)
    {
      if (bsonReader.CurrentBsonType != BsonType.String)
      {
        return string.Empty;
      }

      return bsonReader.ReadString();
    }

    public override void Serialize(
               BsonWriter bsonWriter,
               Type nominalType,
               object value,
               IBsonSerializationOptions options)
    {
      bsonWriter.WriteStartDocument();
      bsonWriter.WriteName("ZipCode");
      bsonWriter.WriteString(value.ToString());
      bsonWriter.WriteEndDocument();
    }
  }
like image 259
user1329380 Avatar asked Feb 02 '23 06:02

user1329380


1 Answers

There are a couple of things going on. The main one is that you have to consume the input regardless of type or the deserialization process gets out of sync. I've tested your scenario writing a custom serializer called ZipCodeSerializer which handles nulls and writes ZipCodes as strings, but accepts either string or ints on input and converts the ints to string.

I used this class to test:

public class Address
{
    public ObjectId Id;
    public string ZipCode;
}

And this is the custom serializer I wrote:

public class ZipCodeSerializer : BsonBaseSerializer
{
    public override object Deserialize(BsonReader bsonReader, Type nominalType, Type actualType, IBsonSerializationOptions options)
    {
        var bsonType = bsonReader.CurrentBsonType;
        switch (bsonType)
        {
            case BsonType.Null:
                bsonReader.ReadNull();
                return null;
            case BsonType.String:
                return bsonReader.ReadString();
            case BsonType.Int32:
                return bsonReader.ReadInt32().ToString();
            default:
                var message = string.Format("ZipCodeSerializer expects to find a String or an Int32, not a {0}.", bsonType);
                throw new BsonSerializationException(message);
        }
    }

    public override void Serialize(BsonWriter bsonWriter, Type nominalType, object value, IBsonSerializationOptions options)
    {
        if (value == null)
        {
            bsonWriter.WriteNull();
        }
        else
        {
            bsonWriter.WriteString((string)value);
        }
    }
}

You have to make sure the custom serializer is hooked up, which you can do like this:

BsonClassMap.RegisterClassMap<Address>(cm =>
    {
        cm.AutoMap();
        cm.GetMemberMap(a => a.ZipCode).SetSerializer(new ZipCodeSerializer());
    });

So now the ZipCode field of the Address class will be handled by the custom serializer.

I created some test data using BsonDocument to make it easy to force particular stored versions of the data in my test collection:

collection.Drop();
collection.Insert(new BsonDocument());
collection.Insert(new BsonDocument("ZipCode", BsonNull.Value));
collection.Insert(new BsonDocument("ZipCode", "12345"));
collection.Insert(new BsonDocument("ZipCode", 56789));

Here's what the documents looked like using the mongo shell:

> db.test.find()
{ "_id" : ObjectId("4f871374e447ad238040e346") }
{ "_id" : ObjectId("4f871374e447ad238040e347"), "ZipCode" : null }
{ "_id" : ObjectId("4f871374e447ad238040e348"), "ZipCode" : "12345" }
{ "_id" : ObjectId("4f871374e447ad238040e349"), "ZipCode" : 56789 }
>

so we see that some ZipCodes are strings and some are ints (there's also a null thrown in).

And this is my test code:

foreach (var document in collection.FindAll())
{
    Console.WriteLine(document.ToJson());
}

And the output of running the test code is:

{ "_id" : ObjectId("4f871374e447ad238040e346"), "ZipCode" : null }
{ "_id" : ObjectId("4f871374e447ad238040e347"), "ZipCode" : null }
{ "_id" : ObjectId("4f871374e447ad238040e348"), "ZipCode" : "12345" }
{ "_id" : ObjectId("4f871374e447ad238040e349"), "ZipCode" : "56789" }
Press Enter to continue

Notice that the zipcode that was an int in the database is now a string.

The full source code of my test program is available at:

http://www.pastie.org/3775465

like image 197
Robert Stam Avatar answered Feb 05 '23 16:02

Robert Stam