JSON serialization is the process of transforming .NET objects into JSON format, which ensures data exchange within applications. Implementing global default JSON serialization settings in ASP.NET Core Web API maintains uniformity across applications. In this article, we’ll explore the various methods for setting global default JSON serialization options in the ASP.NET Core Web API.

To download the source code for this article, you can visit our GitHub repository.

Before we dive into this topic, we recommend going through our article Serialization and Deserialization in C#.

Now, let’s move on.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

Overview of JsonSerializerOptions

The JsonSerializerOptions class is part of the System.Text.Json namespace and offers customization for JSON serialization behavior. It provides various settings that can significantly alter JSON serialization processes to suit specific requirements.

Now, let’s look into some of the essential properties of the JsonSerializerOptions class.

First, we have the PropertyNamingPolicy property, which we use to control the casing of property names in the JSON output, such as using JsonNamingPolicy.CamelCase to start property names with a lowercase letter.

Next, we utilize the DefaultIgnoreCondition property. Setting this to JsonIgnoreCondition.WhenWritingNull ensures that properties with null values are omitted from the JSON output, reducing payload size and potentially enhancing performance.

The Encoder property is next on the list. This property helps prevent XSS attacks by properly encoding JSON data, typically using JavaScriptEncoder.Default.

Moving on, we have the Converters property, which is a list of JsonConverter instances that we use to customize the serialization of certain types that do not serialize as expected by default. This is useful for types like DateTime or custom business objects.

Finally, we have the WriteIndented property, which we use to format the JSON output with indentations and line breaks, making it more readable. While this property is helpful for readability during development, we usually disable it in production to reduce the payload size.

Set Global Default JSON Serialization Options Using GlobalJsonOptions Property

The GlobalJsonOptions property in ASP.NET Core determines how we manage JSON data throughout our application. By setting these options globally, we guarantee uniform handling of JSON data across all parts of our application.

To start with, let’s create a Product class inside our Web API project:

public class Product
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public decimal Price { get; set; }
    public int Quantity { get; set; }
    public DateTime ReleaseDate { get; set; }
    public Manufacturer Manufacturer { get; set; } = new Manufacturer();
}

Also, let’s create a Manufacturer class:

public class Manufacturer
{
    public string? Name { get; set; }
    public string? Location { get; set; }
}

Here, we define two model classes to hold the product’s properties.

Now, let’s configure the JSON serialization options in the Program class:

builder.Services.Configure<JsonOptions>(options =>
{
    options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
    options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
    options.JsonSerializerOptions.WriteIndented = false;
    options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Default;
    options.JsonSerializerOptions.AllowTrailingCommas = true;
    options.JsonSerializerOptions.MaxDepth = 3;
    options.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowReadingFromString;
});

Here, we configure global JSON serialization settings using the JsonOptions class.

First, we set all the essential properties. Additionally, we set AllowTrailingCommas to enhance parser flexibility and MaxDepth to 3 to limit object traversal to three nested levels during serialization. This property may be useful for limiting the depth of nested objects to increase performance and security.

Next, we enable NumberHandling with JsonNumberHandling.AllowReadingFromString to allow parsing numbers from JSON strings into their appropriate numeric types.

Now, let’s create a ProductController class and define a POST method:

[ApiController]
[Route("api/[controller]")]
public class ProductController : ControllerBase
{
    [HttpPost]
    public ActionResult CreateProduct(Product product)
    {
        return Ok(product);
    }
}

Here, we create a simple POST method that accepts the Product object as the input parameter and return it without any modifications.

Let’s take a look at the product JSON data we send to the API in the request body:

{
  "Id": 1,
  "Name": null,
  "Price": 0,
  "Quantity": "5",
  "ReleaseDate": "2024-04-14T10:49:31.813Z",
  "Manufacturer": {
        "Name":"Apple",
        "Location" : "California"
  }
}

We define the JSON properties using the Pascal case. Also, we set the Name property to null and set the Quantity property in the string format.

Now, let’s inspect the response:

{
    "id": 1,
    "price": 0,
    "quantity": 5,
    "releaseDate": "2024-04-14T10:49:31.813Z",
    "manufacturer": {
        "name": "Apple",
        "location": "California"
    }
}

The JSON serializer changes PascalCase names to camelCase and excludes the Name property due to our JsonIgnoreCondition.WhenWritingNull setting.

The NumberHandling property interprets the Quantity as a number despite its string format. Additionally, enabling AllowTrailingCommas and setting MaxDepth property contributes to a successful response.

Set Global Default JSON Serialization Options Using ConfigureHttpJsonOptions

When we create minimal APIs, it’s essential to have fine-grained control over JSON serialization settings specifically for HTTP responses, which means applying particular JSON serialization settings exclusively to the data sent back to clients in HTTP responses.

The ConfigureHttpJsonOptions() extension method allows us to customize JSON options that apply exclusively to the HTTP pipeline, ensuring these settings are isolated from other application parts.

Moving on, let’s set up the ConfigureHttpJsonOptions() method in the Program class:

builder.Services.ConfigureHttpJsonOptions(options =>
{
    options.SerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseUpper;
    options.SerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
    options.SerializerOptions.WriteIndented = false;
    options.SerializerOptions.Encoder = JavaScriptEncoder.Default;
    options.SerializerOptions.AllowTrailingCommas = true;
    options.SerializerOptions.MaxDepth = 3;
    options.SerializerOptions.NumberHandling = JsonNumberHandling.AllowReadingFromString;
});

Here, we add the ConfigureHttpJsonOptions() method to the IServiceCollection. Similarly, we set the JSON serialization properties as we did previously.

Next, let’s define a minimal API in the Program class:

app.MapPost("api/Product/create", (Product product) =>
{
    return product;
});

The serialization settings we defined will apply to this response.

Set Global Default JSON Serialization Options Using Newtonsoft.Json

Newtonsoft.Json also called Json.NET, provides us with various customization options essential for specific applications. These options enable us to adjust how we convert data to and from JSON to meet specific requirements, which may be more challenging with the newer System.Text.Json library in .NET Core 3.0 and beyond. This feature makes Newtonsoft.Json particularly useful when we need more control.

The Newtonsoft.Json package provides us with more options for formatting dates and handling null values. For example, it can serialize dates to strings using various formats and handle null values in multiple ways (ignore, include, or convert to a default).

As a first step, let’s install the Newtonsoft.Json package:

dotnet add package Newtonsoft.Json --version 13.0.3

With this, let’s configure the default settings for JSON serialization in the Program class:

JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
    ContractResolver = new CamelCasePropertyNamesContractResolver(),
    Formatting = Formatting.Indented,
    NullValueHandling = NullValueHandling.Ignore,
    DateFormatString = "dd-MM-yyyy",
    DefaultValueHandling = DefaultValueHandling.Ignore,
    MaxDepth = 3
};

Here, we use the DefaultSettings static property to set the default JsonSerializerSettings for all JSON serialization operations that use the JsonConvert() method within our application.

We then set the default naming strategy for JSON keys to camel case using CamelCasePropertyNamesContractResolver(). We also format the output JSON with indentations for readability. Next, we omit any null properties in the object from the resulting JSON and serialize the date as strings in the “day-month-year” format.

Finally, we handle the default value for properties by setting it to DefaultValueHandling.Ignore, which causes properties with default values (like 0 for integers, false for booleans, etc.) to be excluded from serialization.

Let’s define a POST endpoint in the ProductController class:

[HttpPost("save")]
public ActionResult SaveProduct(Product product)
{
    return Ok(JsonConvert.SerializeObject(product));
}

In this action, we serialize the product object using the JsonConvert.Serialize() method.

So, let’s look at the request body:

{ 
    "Id": 1, 
    "Name": null, 
    "Price": 0, 
    "Quantity":"5", 
    "ReleaseDate": "2024-04-14T10:49:31.813Z", 
    "Manufacturer":
    {
        "Name":"Apple",
        "Location" : "California"
    } 
}

We include JSON properties with Pascal case names and default values for the Name and Price property.

Let’s check the response:

{
    "id": 1,
    "quantity": 5,
    "releaseDate": "14-04-2024",
    "manufacturer": {
        "name": "Apple",
        "location": "California"
    }
}

The Name property is omitted from the response because it contains a null value. Serialization formats the ReleaseDate property in the ‘dd-MM-yyyy’ style, and the Price property is ignored since we have set it to a default value of 0.

Best Practices and Considerations

When we update JSON serialization settings globally, it’s essential to carefully evaluate the potential effects on our existing codebase to avoid unintended consequences. It can alter the behavior of API endpoints that clients have already consumed, potentially breaking contracts if the clients depend on the existing serialization format. We should gradually implement, backed by comprehensive testing and feature toggles, which can help safeguard against disruptions in system behavior.

Consistency is key in JSON serialization practices across an application. It promotes understandability and helps prevent bugs. We must centralize and document the serialization settings to expect uniform behavior throughout the application.

When serialized data forms part of a contract with external systems or requires long-term storage, ensuring compatibility and careful versioning is critical. We should introduce changes through versioned APIs and consider the impact on data storage, processing, and external consumers. This strategic approach will preserve data integrity and support a smooth codebase evolution as new best practices emerge.

When considering the future of .NET, we must consider these additional factors. Microsoft’s System.Text.Json is the default serializer for new .NET applications. Even if we choose to use Newtonsoft.Json, we should be aware of potential shifts in best practices and prepare to adapt our strategy as the ecosystem evolves.

Conclusion

In this article, we’ve explored different ways to set up JSON serialization settings within ASP.NET Core, including utilizing the native options provided by System.Text.Json and the more comprehensive features offered by Newtonsoft.Json.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!