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 library feature comparison 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.
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 @Nerdbank.MessagePack.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 @Nerdbank.MessagePack.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 by serializing the type name after a runtime type check. Deserialization activates an object matching the original type.
Nerdbank.MessagePack generally requires knowing at least something about the type (see Unions) at compile time for security reasons and NativeAOT support. An optional
object
converter can be used to serialize any runtime type for which a shape is available. It will deserialize into maps, arrays, and primitives rather than the original type. 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 |