Can I serialize arbitrary types with protobuf-net?
I'm trying to serialize some objects with pr开发者_StackOverflow中文版otobuf-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.
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
andDateTimeOffsetSurrogate
) - you then use
SetSurrogate(surrogateType)
to educate protobuf-net, for exampleRuntimeTypeModel.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));
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>)
I use this now, is compatible with the serialization as defined in google/protobuf/timestamp.proto
( In proto file: import "google/protobuf/timestamp.proto"; )
[ProtoContract]
public class DateTimeOffsetSurrogate
{
[ProtoMember(1)]
public Int64 Seconds { get; set; }
[ProtoMember(2)]
public Int32 Nanos { get; set; }
public static implicit operator DateTimeOffsetSurrogate(DateTimeOffset value)
{
var totalSeconds = (value - DateTimeOffset.UnixEpoch).TotalSeconds;
var secondsRounded = Convert.ToInt64(totalSeconds);
return new DateTimeOffsetSurrogate { Seconds = secondsRounded, Nanos = Convert.ToInt32((totalSeconds - secondsRounded) * 1000000000) };
}
public static implicit operator DateTimeOffset(DateTimeOffsetSurrogate value)
{
return DateTimeOffset.FromUnixTimeSeconds(value.Seconds).AddTicks(value.Nanos / 100);
}
}
精彩评论