﻿# Json.NET Serializer


This serializer uses [JSON](https://en.wikipedia.org/wiki/Json) via a NuGet dependency on [Json.NET](https://www.newtonsoft.com/json).

## Usage

<!-- snippet: NewtonsoftSerialization -->

```cs
endpointConfiguration.UseSerialization<NewtonsoftJsonSerializer>();
```

<!-- endsnippet -->

### Json.NET attributes

Json.NET attributes are supported.

For example

<!-- snippet: NewtonsoftAttributes -->

```cs
[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; }
}
```

<!-- endsnippet -->

> [!NOTE]
> By default, the Json.NET serializer adds a Byte Order Mark (BOM). To disable it, see the [custom writer](/nservicebus/serialization/newtonsoft.md#usage-custom-writer) section.

### Custom settings

Customize the instance of [JsonSerializerSettings](https://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_JsonSerializerSettings.htm) used for serialization.

<!-- snippet: NewtonsoftCustomSettings -->

```cs
var settings = new JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.Auto,
    Converters =
    {
        new IsoDateTimeConverter
        {
            DateTimeStyles = DateTimeStyles.RoundtripKind
        }
    }
};
var serialization = endpointConfiguration.UseSerialization<NewtonsoftJsonSerializer>();
serialization.Settings(settings);
```

<!-- endsnippet -->

### Custom reader

Customize the creation of the [JsonReader](https://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_JsonReader.htm).

<!-- snippet: NewtonsoftCustomReader -->

```cs
var serialization = endpointConfiguration.UseSerialization<NewtonsoftJsonSerializer>();
serialization.ReaderCreator(stream =>
{
    var streamReader = new StreamReader(stream, Encoding.UTF8);
    return new JsonTextReader(streamReader);
});
```

<!-- endsnippet -->

### Custom writer

Customize the creation of the [JsonWriter](https://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_JsonWriter.htm).

In the example below, the custom writer omits the [Byte Order Mark (BOM)](https://en.wikipedia.org/wiki/Byte_order_mark).

<!-- snippet: NewtonsoftCustomWriter -->

```cs
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
    };
});
```

<!-- endsnippet -->

### Custom content key

When using [additional deserializers](/nservicebus/serialization/index.md#specifying-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](/nservicebus/messaging/headers.md#serialization-headers-nservicebus-contenttype)).


<!-- snippet: NewtonsoftContentTypeKey -->

```cs
var serialization = endpointConfiguration.UseSerialization<NewtonsoftJsonSerializer>();
serialization.ContentTypeKey("custom-key");
```

<!-- endsnippet -->

## TypeNameHandling

The `NewtonsoftJsonSerializer` is using `TypeNameHandling.None` by default.

If `TypeNameHandling.Auto` is required it can be configured via [custom `JsonSerializerSettings` settings](#usage-custom-settings), where the `TypeNameHandling` setting can be explicitly controlled.

> [!WARNING]
> `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](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2326) for further details on this vulnerability.

When using `TypeNameHandling.Auto`, consider a [custom SerializationBinder](https://www.newtonsoft.com/json/help/html/SerializeSerializationBinder.htm) 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](https://www.newtonsoft.com/json/help/html/SerializeTypeNameHandling.htm). This feature requires the `TypeNameHandling` setting to be set to `Auto` or `All`.

See [native integration with SqlTransport sample](/samples/sqltransport/native-integration/index.md) for more details.

## BSON

Customize to use the [Newtonsoft Bson serialization](https://www.newtonsoft.com/json/help/html/SerializeToBson.htm).

<!-- snippet: NewtonsoftBson -->

```cs
var serialization = endpointConfiguration.UseSerialization<NewtonsoftJsonSerializer>();
serialization.ReaderCreator(stream => new BsonDataReader(stream));
serialization.WriterCreator(stream => new BsonDataWriter(stream));
```

<!-- endsnippet -->

## 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](https://www.newtonsoft.com/json/help/html/CustomJsonConverter.htm) as shown below:

<!-- snippet: XContainerJsonConverter -->

```cs
using NewtonsoftJsonSerializer = 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);
    }
}
```

<!-- endsnippet -->

Configure the converter as follows:

<!-- snippet: UseConverter -->

```cs
var settings = new JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.Auto,
    Converters =
    {
        new XmlJsonConverter()
    }
};

var serialization = endpointConfiguration.UseSerialization<NewtonsoftJsonSerializer>();
serialization.Settings(settings);
```

<!-- endsnippet -->

### 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:

```txt
Multiple messages in the same stream are not supported.
```

The `AddDeserializer` API can help transition between serializers. See the [Multiple Deserializers Sample](/samples/serializers/multiple-deserializers/index.md) 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](https://www.newtonsoft.com/json/help/html/SerializeTypeNameHandling.htm). 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](/samples/sqltransport/native-integration/index.md).

To make the serializer compatible with this behavior use the following serialization binder:

<!-- snippet: KnownTypesBinder -->

```cs
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;
    }
}
```

<!-- endsnippet -->

and configured as follows:

<!-- snippet: KnownTypesBinderConfig -->

```cs
endpointConfiguration.UseSerialization<NewtonsoftJsonSerializer>()
    .Settings(new JsonSerializerSettings
    {
        TypeNameHandling = TypeNameHandling.Auto,
        SerializationBinder = new SkipAssemblyNameForMessageTypesBinder(new[]
        {
            typeof(MyNativeIntegrationMessage)
        })
    });
```

<!-- endsnippet -->
