gao-artur / MessagePack.Unionless

The MessagePack unionless formatter

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

MessagePack.Unionless

This implementation is inspired by Phylum123's implementation of PolymorphicMessagePack and relies heavily on TypelessFormatter implementation.

How to use

Implement IFormatterResolver for your base type

public sealed class MyBaseTypeFormatterResolver : IFormatterResolver
{
    private static readonly UnionlessFormatter<IMyBaseType> Formatter = new();

    private MyBaseTypeFormatterResolver()
    {
    }

    public static IFormatterResolver Instance { get; } = 
        new MyBaseTypeFormatterResolver();

    public IMessagePackFormatter<T>? GetFormatter<T>()
    {
        return typeof(T) == typeof(IMyBaseType)
            ? (IMessagePackFormatter<T>)Formatter
            : null;
    }
}

Create the UnionlessMessagePackSerializerOptions with the previously created formatter resolver as the first in the chain

var options = new UnionlessMessagePackSerializerOptions(
    MessagePackSerializer.DefaultOptions
        .WithResolver(CompositeResolver.Create(
            MyBaseTypeFormatterResolver.Instance,  // <== your resolver should be first
            StandardResolver.Instance)))
{
    TypeHeaderFormatter = new TypeIdTypeHeaderFormatter(
        new Dictionary<Type, int>
        {
            [typeof(DerivedClass)] = 0,
            [typeof(DerivedStruct)] = 1
        }
    )
};

It's up to you how to fill the type-to-id mapping. It can be a predefined dictionary, dynamic registry, or automatic discovery on application startup.

Now you can serialize and deserialize types without decorating the base type with Union attribute

public interface IMyBaseType
{
    string? BaseProp { get; set; }
}

public abstract class MyBaseType : IMyBaseType
{
    [Key(0)]
    public string? BaseProp { get; set; }
}

[MessagePackObject]
public class DerivedClass : MyBaseType
{
    [Key(1)]
    public string? DerivedTypeProp { get; set; }
}

[MessagePackObject]
public struct DerivedStruct : IMyBaseType
{
    [Key(0)]
    public string? BaseProp { get; set; }

    [Key(1)]
    public string? DerivedStructProp { get; set; }
}

var before = new DerivedClass
{
    BaseProp = $"Base of {nameof(DerivedClass)}",
    DerivedTypeProp = nameof(DerivedClass)
};

var bin = MessagePackSerializer.Serialize<IMyBaseType>(before, options);
var debugJson = MessagePackSerializer.ConvertToJson(bin);
// [0,["Base of DerivedClass","DerivedClass"]]
Trace.WriteLine(debugJson);

var after = MessagePackSerializer.Deserialize<IMyBaseType>(bin, options);

Alternative ITypeHeaderFormatter implementations

This repo provides an alternative implementation to encode the actual type information into the result binary data. The TypeNameTypeHeaderFormatter can be used without a type-to-id mapping and will encode the full type name.

Benchmark

BenchmarkDotNet=v0.13.5, OS=Windows 10 (10.0.19045.2965/22H2/2022Update)
11th Gen Intel Core i7-11800H 2.30GHz, 1 CPU, 16 logical and 8 physical cores
.NET SDK=7.0.302
  [Host]     : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
  DefaultJob : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2

Benchmark results

About

The MessagePack unionless formatter


Languages

Language:C# 99.9%Language:Batchfile 0.1%