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.
Before we dive into this topic, we recommend going through our article Serialization and Deserialization in C#.
Now, let’s move on.
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.