简体   繁体   中英

Custom BSON Key Value Serializer

I ran into some trouble trying to convert a class to a BSON document.

I have Custom1 and Custom2 which should behave a little different. How do I create a custom serializer which "unfold" the KeyValuePair so it generates the expected result (See below)? You can see the code example below together with the expected result.

Moreover, I'm using Mongo BSON library for serializing the object.

public class UserData
{
    public UserData()
    {
        Id = 100;
        Name = "Superuser";
        Custom1 = new KeyValuePair<string, double>("HelloWorld1", 1);
        Custom2 = new KeyValuePair<string, double>("HelloWorld2", 2);
    }

    public int Id { get; set; }
    public string Name { get; set; }
    public KeyValuePair<string, double> Custom1 { get; set; }
    public KeyValuePair<string, double> Custom2 { get; set; }
}

Execute test code:

var userdata = new UserData();
var doc = userdata.ToBsonDocument();

Current result:

{
    "Id": 100,
    "Name": "Superuser",
    "Custom1": {
                    "Key": "HelloWorld1",
                    "Value": 1
               },
    "Custom2": {
                    "Key": "HelloWorld2",
                    "Value": 2
               }
}

Expected result:

{
    "Id": 100,
    "Name": "Superuser",
    "HelloWorld1": 1,
    "HelloWorld2": 2
}

For such a complex serialization case you have to implement custom IBsonSerializer converter for your class.

Here is the working example:

public class UserDataSerializer : SerializerBase<UserData>
{
    public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, UserData value)
    {
        context.Writer.WriteStartDocument();
        context.Writer.WriteName("Id");
        context.Writer.WriteInt32(value.Id);
        context.Writer.WriteName("Name");
        context.Writer.WriteString(value.Name);

        WriteKeyValue(context.Writer, value.Custom1);
        WriteKeyValue(context.Writer, value.Custom2);

        context.Writer.WriteEndDocument();
    }

    private void WriteKeyValue(IBsonWriter writer, KeyValuePair<string, double> kv)
    {
        writer.WriteName(kv.Key);
        writer.WriteDouble(kv.Value);
    }

    public override UserData Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
    {
        //TODO: implement data deserialization using context.Reader
        throw new NotImplementedException();
    }
}

To make it work you also need to register our UserDataSerializer somehow. IBsonSerializationProvider is the easiest way to achieve that.

public class UserDataSerializationProvider : IBsonSerializationProvider
{
    public IBsonSerializer GetSerializer(Type type)
    {
        if (type == typeof(UserData)) return new UserDataSerializer();
        return null;
    }
}

And finally we can use it.

//register our serialization provider
BsonSerializer.RegisterSerializationProvider(new UserDataSerializationProvider());

var userdata = new UserData();
var doc = userdata.ToBsonDocument();

Here is the result:

{ "Id" : 100, "Name" : "Superuser", "HelloWorld1" : 1.0, "HelloWorld2" : 2.0 }

You may also want to implement UserDataSerializer.Deserialize() method in order to provide the backward conversion. It can be done in the same way using context.Reader .

More information about custom serialization process can be found here .

If you are using the driver 2.0 or higher, you could try to make your class into a DynamicObject, like this:

public class UserData : DynamicObject
{
 public UserData()
  {
    Id = 100;
    Name = "Superuser";
    Custom1 = new KeyValuePair<string, double>("HelloWorld1", 1);
    Custom2 = new KeyValuePair<string, double>("HelloWorld2", 2);
  }

 public id Id { get; set; }
 public string Name { get; set; }
 public KeyValuePair<string, double> Custom1 { get; set; }
 public KeyValuePair<string, double> Custom2 { get; set; }

 public override bool TryGetMember(
        GetMemberBinder binder, out object result)
    {
        string name = binder.Name;
        result = null;
        if(name.Equals(Custom1.Key)) 
            result = Custom1.Value;
        else if(name.Equals(Custom2.Key))
            result = Custom2.Value;
        return result != null;
    }

  public override bool TrySetMember(
        SetMemberBinder binder, object value)
    {         
      string name = binder.Name;
      if(name.Equals(Custom1.Key)) 
        Custom1.Value = value;
      else if(name.Equals(Custom2.Key))
        Custom2.Value = value;
      return name.Equals(Custom1.Key) || 
             name.Equals(Custom2.Key);
    }
  }

According to the release documentation, you should then be able to do this:

var userdata = new UserData(); var doc = ((dynamic) userdata).ToBsonDocument();

And get the expected format.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM