How can I serialize a struct as a primitive type?

Viewed 88

I have an enum that is serialized and sent over the wire. Requirements have evolved and I need to support additional values that may be defined in other assemblies, but I can't break binary compatibility for the who-knows-what that is in the wild.

My initial idea was to replace this enum with a enum-like struct wrapping an integer and use a surrogate to serialize it as an integer:

public readonly struct SomeFlag
{
    private readonly int _value;

    public SomeFlag(int value) { _value = value; }

    public static implicit operator int(SomeFlag type)
    {
        return type._value;
    }

    public static implicit operator SomeFlag(int value)
    {
        return new SomeFlag(value);
    }
}

...

TypeModel
    .Add(typeof(SomeFlag), false)
    .SetSurrogate(typeof(int));

When I try to serialize SomeFlag, I get an error: Data of this type has inbuilt behaviour, and cannot be added to a model in this way: System.Int32

Aside from replacing the structs in my models and code with plain integers, is there any way to make a non-primitive type serialize as a primitive?

1 Answers

Yes, but it requires a custom serializer in v3+ of the library;

using ProtoBuf;
using ProtoBuf.Serializers;

var obj = new HazSomeFlag { Value = new SomeFlag(42) };
var ms = new MemoryStream();
Serializer.Serialize(ms, obj);
if (!ms.TryGetBuffer(out var buffer)) buffer = ms.ToArray();
var hex = BitConverter.ToString(buffer.Array!, buffer.Offset, buffer.Count);
Console.WriteLine(hex); // 08-2A === field 1, varint = 42
ms.Position = 0;
var clone = Serializer.Deserialize<HazSomeFlag>(ms);
Console.WriteLine(clone.Value);
var schema = Serializer.GetProto<HazSomeFlag>();
Console.WriteLine(schema);

[ProtoContract]
public class HazSomeFlag
{
    [ProtoMember(1)]
    public SomeFlag Value { get; set; }
}
[ProtoContract(Serializer = typeof(MySerializer), Name = "int32")]
public readonly struct SomeFlag
{
    public override string ToString() => $"SomeFlag: {_value}";
    private readonly int _value;

    public SomeFlag(int value) { _value = value; }

    public static implicit operator int(SomeFlag type)
    {
        return type._value;
    }

    public static implicit operator SomeFlag(int value)
    {
        return new SomeFlag(value);
    }

    class MySerializer : ISerializer<SomeFlag>
    {
        SerializerFeatures ISerializer<SomeFlag>.Features
            => SerializerFeatures.CategoryScalar | SerializerFeatures.WireTypeVarint;

        SomeFlag ISerializer<SomeFlag>.Read(ref ProtoReader.State state, SomeFlag value)
            => new SomeFlag(state.ReadInt32());

        void ISerializer<SomeFlag>.Write(ref ProtoWriter.State state, SomeFlag value)
            => state.WriteInt32(value._value);
    }
}

which works (as shown) compatibly with the schema:

syntax = "proto3";

message HazSomeFlag {
   int32 Value = 1;
}
Related