Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I serialize arbitrary types with protobuf-net?

Tags:

protobuf-net

I'm trying to serialize some objects with protobuf-net, but unfortunately they make liberal use of DateTimeOffset, which is not yet supported by protobuf-net. This leads to lots of:

No serializer defined for type: System.DateTimeOffset

Can I define my own serialization routine for unknown types? (The same question was asked earlier, but his problem was worked around.)

I'm using the latest protobuf-net beta, v2.0.0.431, under .NET 4 if it matters. I'm also using runtime definitions, so I have no way to declaratively specify how certain properties are to be handled.

like image 915
ladenedge Avatar asked Aug 12 '11 21:08

ladenedge


3 Answers

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));
like image 134
Marc Gravell Avatar answered Nov 10 '22 08:11

Marc Gravell


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));
like image 25
Max Shmelev Avatar answered Nov 10 '22 08:11

Max Shmelev


Just in case any F# developers come across this question, here's an answer in F#:

[<ProtoContract>]
type DateTimeOffsetSurrogate() =
    [<ProtoMember(1)>]
    member val DateTimeString = "" with get, set
    static member public op_Implicit(value : DateTimeOffset) : DateTimeOffsetSurrogate =
        DateTimeOffsetSurrogate(DateTimeString = value.ToString("o"))
    static member public op_Implicit(value : DateTimeOffsetSurrogate) : DateTimeOffset =
        DateTimeOffset.Parse(value.DateTimeString)

It's the op_Implicit aspect which is non-obvious.

You could also adapt this to use Max's technique of using ticks to save space.

Edit: Here's how to add the surrogate to the run-time-type model thingy:

let init() =
    ProtoBuf.Meta.RuntimeTypeModel.Default.Add(typedefof<DateTimeOffset>, false).SetSurrogate(typedefof<DateTimeOffsetSurrogate>)
like image 3
Kit Avatar answered Nov 10 '22 07:11

Kit