Can I serialize arbitrary types with protobuf-net?

There are two ways of approaching the issue of unknown "common" types; the first is to use a shim property, for example a property that represents the value as something similar (a string or long for example):

[ProtoMember(8)]
public string Foo {
    get { ... read from the other member ... }
    set { ... assign the other member ... }
}

The other approach is a surrogate, which is a second protobuf contract that is automatically substituted. The requirements to use a surrogate are:

  • there must be a defined conversion operator (implicit or explict) between the two types (for example, DateTimeOffset and DateTimeOffsetSurrogate)
  • you then use SetSurrogate(surrogateType) to educate protobuf-net, for example RuntimeTypeModel.Default.Add(typeof(DateTimeOffset), false).SetSurrogate(typeof(DateTimeOffsetSurrogate));

the shim property is simpler, but requires repeat per-member. The surrogate is applied automatically to all instances of the type within the model. The surrogate then follows standard protobuf-net rules, so you would indicate which members to serialize, etc.

EDIT: Adding code example

using System;
using ProtoBuf;

[ProtoContract]
public class DateTimeOffsetSurrogate
{
    [ProtoMember(1)]
    public string DateTimeString { get; set; }

    public static implicit operator DateTimeOffsetSurrogate(DateTimeOffset value)
    {
        return new DateTimeOffsetSurrogate {DateTimeString = value.ToString("u")};
    }

    public static implicit operator DateTimeOffset(DateTimeOffsetSurrogate value)
    {
        return DateTimeOffset.Parse(value.DateTimeString);
    }
}

Then register it like this

RuntimeTypeModel.Default.Add(typeof(DateTimeOffset), false).SetSurrogate(typeof(DateTimeOffsetSurrogate));

With all respect to Marc Gravell's answer, if you care about the size of serialized data, you should use the following surrogate class. The output size is 21 bytes instead of 35 bytes.

using System;
using ProtoBuf;

[ProtoContract]
public class DateTimeOffsetSurrogate
{
    [ProtoMember(1)]
    public long DateTimeTicks { get; set; }
    [ProtoMember(2)]
    public short OffsetMinutes { get; set; }

    public static implicit operator DateTimeOffsetSurrogate(DateTimeOffset value)
    {
        return new DateTimeOffsetSurrogate
        {
            DateTimeTicks = value.Ticks,
            OffsetMinutes = (short)value.Offset.TotalMinutes
        };
    }

    public static implicit operator DateTimeOffset(DateTimeOffsetSurrogate value)
    {
        return new DateTimeOffset(value.DateTimeTicks, TimeSpan.FromMinutes(value.OffsetMinutes));
    }
}

And then registering it absolutely the same way:

RuntimeTypeModel.Default.Add(typeof(DateTimeOffset), false).SetSurrogate(typeof(DateTimeOffsetSurrogate));

Tags:

Protobuf Net