Getting Started
Architecture
NServiceBus
Transports
Persistence
ServiceInsight
ServicePulse
ServiceControl
Monitoring
Samples

Json.NET Serializer

Target Version: NServiceBus 8.x

This serialiser uses JSON via a NuGet dependency on Json.NET.

Usage

endpointConfiguration.UseSerialization<NewtonsoftJsonSerializer>();

Json.NET attributes

Json.NET attributes are supported.

For example

[JsonObject(MemberSerialization.OptIn)]
public class CreatePersonMessage :
    IMessage
{
    // "John Smith"
    [JsonProperty]
    public string Name { get; set; }

    // "2000-12-15T22:11:03"
    [JsonProperty]
    public DateTime BirthDate { get; set; }

    // new Date(976918263055)
    [JsonProperty]
    [JsonConverter(typeof(JavaScriptDateTimeConverter))]
    public DateTime LastModified { get; set; }

    // not serialized because mode is opt-in
    public string Department { get; set; }
}
By default, the Json.NET serializer adds a Byte Order Mark (BOM). To disable it, see the custom writer section.

Custom settings

Customize the instance of JsonSerializerSettings used for serialization.

var settings = new JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.Auto,
    Converters =
    {
        new IsoDateTimeConverter
        {
            DateTimeStyles = DateTimeStyles.RoundtripKind
        }
    }
};
var serialization = endpointConfiguration.UseSerialization<NewtonsoftJsonSerializer>();
serialization.Settings(settings);

Custom reader

Customize the creation of the JsonReader.

var serialization = endpointConfiguration.UseSerialization<NewtonsoftJsonSerializer>();
serialization.ReaderCreator(stream =>
{
    var streamReader = new StreamReader(stream, Encoding.UTF8);
    return new JsonTextReader(streamReader);
});

Custom writer

Customize the creation of the JsonWriter.

In the example below, the custom writer omits the Byte Order Mark (BOM).

var noBomEncoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: false);

var serialization = endpointConfiguration.UseSerialization<NewtonsoftJsonSerializer>();
serialization.WriterCreator(stream =>
{
    var streamWriter = new StreamWriter(stream, noBomEncoding);
    return new JsonTextWriter(streamWriter)
    {
        Formatting = Formatting.None
    };
});

Custom content key

When using additional deserializers or transitioning between different versions of the same serializer it can be helpful to take explicit control over the content type a serializer passes to NServiceBus (to be used for the ContentType header).

var serialization = endpointConfiguration.UseSerialization<NewtonsoftJsonSerializer>();
serialization.ContentTypeKey("custom-key");

TypeNameHandling

The NewtonsoftJsonSerializer is using TypeNameHandling.None by default.

If TypeNameHandling.Auto is required it can be configured via custom JsonSerializerSettings settings, where the TypeNameHandling setting can be explicitly controlled.

TypeNameHandling.Auto can be a security risk as it allows the message payload to control the deserialization target type. See CA2326: Do not use TypeNameHandling values other than None for further details on this vulnerability.

When using TypeNameHandling.Auto, consider a custom SerializationBinder to limit the allowed deserialization types.

Inferring message type from $type

For integration scenarios where the sender is unable to add message headers, the serializer is able to infer the message type from the $type property supported by Json.NET. This feature requires the TypeNameHandling setting to be set to Auto or All.

See native integration with SqlTransport sample for more details.

BSON

Customize to use the Newtonsoft Bson serialization.

var serialization = endpointConfiguration.UseSerialization<NewtonsoftJsonSerializer>();
serialization.ReaderCreator(stream => new BsonDataReader(stream));
serialization.WriterCreator(stream => new BsonDataWriter(stream));

Compatibility with the core JSON serializer

Up to NServiceBus version 6, a JSON serializer based on Json.NET was bundled with the core package. This section outlines the compatibility considerations when switching to this serializer.

No support for XContainer and XDocument properties

In contrast to the bundled serializer XContainer and XDocument properties are no longer supported. If XContainer and XDocument properties are required use a JsonConverter as shown below:

using NewtonsoftJsonSerializer = global::Newtonsoft.Json.JsonSerializer;

class XmlJsonConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, NewtonsoftJsonSerializer serializer)
    {
        var xcontainer = (XContainer) value;
        writer.WriteValue(xcontainer.ToString(SaveOptions.DisableFormatting));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, NewtonsoftJsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
        {
            return null;
        }

        if (reader.TokenType != JsonToken.String)
        {
            throw new Exception($"Unexpected token or value when parsing XContainer. Token: {reader.TokenType}, Value: {reader.Value}");
        }

        var value = (string) reader.Value;
        if (objectType == typeof(XDocument))
        {
            try
            {
                return XDocument.Load(new StringReader(value));
            }
            catch (Exception exception)
            {
                throw new Exception($"Error parsing XContainer string: {reader.Value}", exception);
            }
        }

        return XElement.Load(new StringReader(value));
    }

    public override bool CanConvert(Type objectType)
    {
        return typeof(XContainer).IsAssignableFrom(objectType);
    }
}

Configure the converter as follows:

var settings = new JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.Auto,
    Converters =
    {
        new XmlJsonConverter()
    }
};
var serialization = endpointConfiguration.UseSerialization<NewtonsoftJsonSerializer>();
serialization.Settings(settings);

No support for bundled logical messages

This serializer is not compatible with multiple bundled messages (when using the Send(object[] messages) APIs) sent from NServiceBus version 3 and below. If this scenario is detected then an exception with the following message will be thrown:

Multiple messages in the same stream are not supported.

The AddDeserializer API can help transition between serializers. See the Multiple Deserializers Sample for more information.

Use of $type requires an assembly qualified name

The bundled serializer registers a custom serialization binder in order to not require the assembly name to be present when inferring message type from the $type property supported by json.net. Not having to specify the assembly name can be useful to reduce coupling when using the serializer for native integration scenarios as demonstrated in this sample.

To make the serializer compatible with this behavior use the following serialization binder:

class SkipAssemblyNameForMessageTypesBinder : ISerializationBinder
{
    Type[] messageTypes;

    public SkipAssemblyNameForMessageTypesBinder(Type[] messageTypes)
    {
        this.messageTypes = messageTypes;
    }

    public Type BindToType(string assemblyName, string typeName)
    {
        return messageTypes.FirstOrDefault(messageType => messageType.FullName == typeName);
    }

    public void BindToName(Type serializedType, out string assemblyName, out string typeName)
    {
        assemblyName = serializedType.Assembly.FullName;
        typeName = serializedType.FullName;
    }
}

and configured as follows:

endpointConfiguration.UseSerialization<NewtonsoftJsonSerializer>()
    .Settings(new JsonSerializerSettings
    {
        TypeNameHandling = TypeNameHandling.Auto,
        SerializationBinder = new SkipAssemblyNameForMessageTypesBinder(new[]
        {
            typeof(MyNativeIntegrationMessage)
        })
    });

Samples


Last modified