Table of Contents

Migrating from MessagePack-CSharp

If you are migrating from MessagePack-CSharp, or considering doing so, this document is for you.

You should probably start by reviewing the features of each library to make sure that the transition has the possibility of being successful. If you see a feature is missing from Nerdbank.MessagePack that you need, look for an issue for it and give it a 👍🏻 vote, or file a new issue if you don't see one.

Feature comparison

See how this library compares to other .NET MessagePack libraries.

In many cases, the ✅ or ❌ in the table below are hyperlinks to the relevant documentation or an issue you can vote up to request the feature.

Feature Nerdbank.MessagePack MessagePack-CSharp
Optimized for high performance
Contractless data types 1
Attributed data types
Polymorphic serialization 2
Skip serializing default values
Dynamically use maps or arrays for most compact format
Typeless serialization
Custom converters
Stateful converters
Deserialization callback
MsgPack extensions
LZ4 compression
Trim-safe
NativeAOT ready 3
Unity 4
Async
Reference preservation
JSON schema export
Secure defaults
Automatic hash collection deserialization in secure mode
Automatic collision-resistant hash function for arbitrary types
Free of mutable statics

Security is a complex subject, and an area where Nerdbank.MessagePack is actively evolving. Learn more about how to secure your deserializer.

Migration process

To migrate from MessagePack-CSharp to Nerdbank.MessagePack, begin by adding a package reference to Nerdbank.MessagePack as described in the Getting Started guide.

With the new package referenced, automated code fixes are immediately provided to aid in the mechanics of migration. You should complete migration before removing references to the old MessagePack package so the analyzers and code fixes can do their best work.

The migration analyzers produce diagnostics that are not errors or warnings, so you may need to navigate to an actual piece of code using or implementing a type from MessagePack-CSharp and activate the code fixes menu (Ctrl+. or Quick Actions in Visual Studio) to see the migration options. When you activate the migration code fix, you will have the option to apply the code fix to all occurrences in the solution rather than just the one you found, which can speed up your migration process.

Sometimes after applying one migration code fix, a subsequent analyzer will report new diagnostics, guiding you to the next step in migration.

The following sections demonstrate the changes that are required to migrate from MessagePack-CSharp to Nerdbank.MessagePack. Remember that automated code fixes can do most or all of this for you.

MessagePackObjectAttribute

MessagePack-CSharp recommends that every user data type be annotated with [MessagePackObject] to enable automatic serialization. In fact unless you use [MessagePackObject(true)], you must also annotate every field or property with [Key(0)], [Key(1)], etc., and members that should not be serialized with [IgnoreMember].

With Nerdbank.MessagePack, you can remove the [MessagePackObject] attribute from your types, as it is not required. Nerdbank.MessagePack supports something of a hybrid between MessagePack-CSharp's [MessagePackObject] and "contractless" modes, to achieve something much easier to use and yet flexible when you need to tweak it.

Top-level classes and structs that you need to serialize (that is, the ones you pass directly to Serialize or Deserialize) need only that you annotate the type with GenerateShapeAttribute. Such annotated types must be declared with the partial modifier to enable source generation to add the necessary serialization code to the type. Learn more about this in our Getting Started guide.

-[MessagePackObject]
+[GenerateShape] // only necessary if you pass `MyType` directly to the MessagePackSerializer or KnownSubTypeAttribute
 public class MyType
 {
 }

If your type implements MessagePack.IMessagePackSerializationCallbackReceiver, you should implement change this to implement IMessagePackSerializationCallbacks instead.

KeyAttribute

Nerdbank.MessagePack also supports KeyAttribute, which serves the same function as in MessagePack-CSharp: to change the serialized schema from that of a map of property name=value to an array of values. Thus, you may keep the [Key(0)], [Key(1)], etc., attributes on your types if you wish to maintain the schema of the serialized data, provided you change the namespace.

If using [Key("name")] attributes as a means to change the serialized property names, this must be replaced with PropertyShapeAttribute with PropertyShapeAttribute.Name set to the serialized name.

-[Key("name")]
+[PropertyShape(Name = "name")]
 public string SomeProperty { get; set; }

IgnoreMemberAttribute

The [IgnoreMemberAttribute] that comes from MessagePack-CSharp can be removed from non-public members, which are never considered for serialization by default. For public members that should be ignored, replace this attribute with PropertyShapeAttribute with PropertyShapeAttribute.Ignore set to true.

-[IgnoreMember]
+[PropertyShape(Ignore = true)]
 public int SomeProperty { get; set; }

-[IgnoreMember]
 internal int AnotherProperty { get; set; }

UnionAttribute

MessagePack-CSharp defines a UnionAttribute by which you can serialize an object when you know its base type or interface at compile-time, but whose exact type is not known until runtime, provided you can predict the closed set of allowed runtime types in advance. Nerdbank.MessagePack supports this same use case via its KnownSubTypeAttribute, and migration is straightforward:

-[Union(0, typeof(MyType1))]
-[Union(1, typeof(MyType2))]
+[KnownSubType(typeof(MyType1), 0)]
+[KnownSubType(typeof(MyType2), 1)]
 public interface IMyType
 {
 }

Any types referenced by the KnownSubTypeAttribute must be annotated with GenerateShapeAttribute as described above.

IMessagePackFormatter<T>

MessagePack-CSharp allows you to define custom formatters for types that it doesn't know how to serialize by default by implementing the IMessagePackFormatter<T> interface. In Nerdbank.MessagePack, that use case is addressed by deriving a class from the MessagePackConverter<T> class. These two APIs are very similar, but the method signatures are slightly different, as well as the patterns that provide security mitigations.

-public class MyTypeFormatter : IMessagePackFormatter<MyType>
+public class MyTypeFormatter : MessagePackConverter<MyType>
 {
-    public MyType Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
+    public override MyType Read(ref MessagePackReader reader, SerializationContext context)
     {
         if (reader.TryReadNil())
         {
             return null;
         }

         string name = null;
-        options.Security.DepthStep(ref reader);
+        context.DepthStep();
-        try
-        {
             int count = reader.ReadArrayHeader();
             for (int i = 0; i < count; i++)
             {
                 switch (i)
                 {
                     case 0:
-                        name = options.Resolver.GetFormatterWithVerify<string>().Deserialize(ref reader, options);
+                        name = context.GetConverter<string>().Read(ref reader, context);
                         break;
                     default:
-                        reader.Skip();
+                        reader.Skip(context);
                         break;
                     }
             }

             return new MyType { Name = name };
-        }
-        finally
-        {
-            reader.Depth--;
-        }
     }

-    public void Serialize(ref MessagePackWriter writer, MyType value, MessagePackSerializerOptions options)
+    public override void Write(ref MessagePackWriter writer, in MyType value, SerializationContext context)
     {
         if (value is null)
         {
             writer.WriteNil();
             return;
         }

         writer.WriteArrayHeader(1);
-        options.Resolver.GetFormatterWithVerify<string>().Serialize(ref writer, value.Name, options);
+        context.GetConverter<string>().Write(ref writer, value.Name, context);
     }
 }

MessagePackFormatterAttribute

A custom type may be annotated with the MessagePackFormatterAttribute to specify a custom formatter for that type. In Nerdbank.MessagePack, this attribute is replaced with the MessagePackConverterAttribute in a straightforward replacement.

-[MessagePackFormatter(typeof(MyTypeFormatter))]
+[MessagePackConverter(typeof(MyTypeFormatter))]
 public class MyType
 {
     public string Name { get; set; }
 }

Security mitigations

In MessagePack-CSharp, security mitigations are provided by the MessagePackSecurity class, as referenced by the MessagePackSerializerOptions class.

In Nerdbank.MessagePack, security mitigations are provided by the SerializationContext struct, as referenced by MessagePackSerializer.StartingContext.

Incompatibilities

Some functionality in MessagePack-CSharp has no equivalent in Nerdbank.MessagePack, as follows:

  • Typeless serialization: MessagePack-CSharp supports serializing and deserializing objects without knowing their type at compile time. Nerdbank.MessagePack requires knowing at least something about the type (see Unions) at compile time for security reasons and NativeAOT support. Custom converters can be written to overcome these limitations where required.

Other API changes

Many APIs are exactly the same or very similar. In some cases, APIs offering equivalent or similar functionality have been renamed. To help with migration, the following table lists some of the most common APIs that have changed names.

MessagePack-CSharp Nerdbank.MessagePack
ExtensionResult Extension
MessagePackReader.ReadExtensionFormat MessagePackReader.ReadExtension()
MessagePackReader.ReadExtensionFormatHeader MessagePackReader.ReadExtensionHeader()
MessagePackWriter.WriteExtensionFormat MessagePackWriter.Write(Extension)
MessagePackWriter.WriteExtensionFormatHeader MessagePackWriter.Write(ExtensionHeader)
IMessagePackSerializationCallbackReceiver IMessagePackSerializationCallbacks

  1. Nerdbank.MessagePack's approach is more likely to be correct by default and more flexible to fixing when it is not.

  2. MessagePack-CSharp is limited to derived types that can be attributed on the base type, whereas Nerdbank.MessagePack allows for dynamically identifying derived types at runtime.

  3. Although MessagePack-CSharp does not support .NET 8 flavor NativeAOT, it has long-supported Unity's il2cpp runtime, but it requires careful avoidance of dynamic features.

  4. This hasn't been tested, and even if it works, the level of active support may be limited as the maintainers of Nerdbank.MessagePack do not use Unity. We may accept outside contributions to support it if it isn't onerous to maintain.