How would I write a custom protobuf-net serializer for this particular class

20 Views Asked by At

I am trying to write a custom protobuf-net serializer to put a property of type object to the wire.

[ProtoContract(Serializer=typeof(ChangeRecordSerializer))]
public class ChangeRecord
{
   public object NewValue {get;set;}
}

This is where I'm at:

public class ChangeRecordSerializer : ISerializer<ChangeRecord>
{
   public void Write(ref ProtoWriter.State state, ChangeRecord value)
   {
      state.WriteString(1, value.NewValue.GetType().FullName;
      // how to serialize value.NewValue???
   }

   public void Read(ref ProtoReader.State state, ChangeRecord value)
   {
      int num = 0;
      string typeName = null;
      while ((num = state.ReadFieldHeader()) > 0)
      {
         switch (num)
         {
            case 1:
               typeName = state.ReadString();
               break;

            case 2:
               var type = Type.GetType(typeName);
               // how to read value.NewValue???
               break;
         }
      }
   }

   public SerializerFeatures Features => SerializerFeatures.CategoryMessage | SerializerFeatures.WireTypeString;
}

How do I implement the two commented out lines?

1

There are 1 best solutions below

0
Marc Gravell On

The problem with using a custom serializer is that (at least currently) once you're at that level: you need to stay at that level - meaning: you can't just get the serializer to handle all the expected types: it is expecting you to be explicit from there downwards, which gets very awkward. If this was me, I would just use a surrogate, having the surrogate work similarly to a discriminated union. This doesn't need to be expensive; consider:

using ProtoBuf;

Show(32);
Show("abc");
Show(new Foo { Bar = "banana" });

static void Show(object value)
{
    var obj = new ChangeRecord { NewValue = value };
    var clone = Serializer.DeepClone(obj);
    Console.WriteLine($"{obj.NewValue} => {clone.NewValue}");
}


[ProtoContract(Surrogate = typeof(ChangeRecordSurrogate))]
public class ChangeRecord
{
    public object? NewValue { get; set; }
}


[ProtoContract]
public class Foo
{
    [ProtoMember(1)]
    public string? Bar { get; set; }
    public override string ToString() => $"Foo: {Bar}";
}


[ProtoContract]
struct ChangeRecordSurrogate
{
    public static implicit operator ChangeRecordSurrogate(ChangeRecord value)
        => new ChangeRecordSurrogate { Value = value?.NewValue };
    public static implicit operator ChangeRecord(ChangeRecordSurrogate value)
        => new ChangeRecord { NewValue = value.Value! };

    public object? Value { get; set; }

    [ProtoMember(1)]
    public int? ValueInt32
    {
        get => Value is int x ? x : default;
        set => Value = value;
    }

    [ProtoMember(2)]
    public string? ValueString
    {
        get => Value as string;
        set => Value = value;
    }

    [ProtoMember(3)]
    public Foo? ValueFoo
    {
        get => Value as Foo;
        set => Value = value;
    }
}