ASP.NET Core – Configure JSON serializer options

ASP.NET Core uses System.Text.Json as the default JSON serializer. To configure the JSON serializer options, call AddJsonOptions() in the initialization code:

using System.Text.Json.Serialization;

//rest of adding services

builder.Services.AddControllers().AddJsonOptions(options =>
{
    options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
});

//rest of init code
Code language: C# (cs)

Calling AddJsonOptions() gives you access to the global JsonSerializerOptions object. This is used for all requests / responses. You can configure the options and add converters (including your own custom JSON converters).

ASP.NET Core’s default JSON options

By default, ASP.NET Core uses JsonSerializerDefaults.Web with JsonSerializerOptions. This is equivalent to using these settings:

PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
NumberHandling = JsonNumberHandling.AllowReadingFromString
Code language: C# (cs)

You can configure this however you want in AddJsonOptions().

Return JsonResult with JsonSerializerOptions

Using AddJsonOptions() configures JSON options for all requests / responses. If you want to configure the JSON options for a single response, use JsonResult. You can return JsonResult with a JsonSerializerOptions configured how you like. Here’s an example:

using System.Text.Json;
using System.Text.Json.Serialization;
using System.Net;

[HttpGet("{symbol}")]
public async Task<IActionResult> Get(string symbol)
{
	var stock = await GetStockFromRepo(symbol);

	var options = new JsonSerializerOptions(JsonSerializerDefaults.Web);
	options.Converters.Add(new JsonStringEnumConverter());

	return new JsonResult(stock, options)
	{
		StatusCode = (int)HttpStatusCode.OK
	};
}
Code language: C# (cs)

Note: Notice it’s passing in JsonSerializerDefaults.Web into the constructor. This is to make sure it’s using the defaults that ASP.NET Core normally uses.

It uses the JsonSerializerOptions you passed in to serialize the model in the response. This endpoint outputs the following JSON:

{
    "symbol": "AMZN",
    "price": 101.1,
    "quoteTime": "2021-07-23T15:13:16.3911373-04:00",
    "fundType": "Stock"
}
Code language: JSON / JSON with Comments (json)

Performance tradeoff

This approach is simple but it has a performance tradeoff. You get optimal performance by reusing JsonSerializerOptions objects. The performance hit may be miniscule when compared to the overall response time, so it probably won’t make a big difference.

Change Newtonsoft settings

System.Text.Json is the default JSON serializer in ASP.NET Core. Let’s say you want to use Newtonsoft instead, and you want to change the settings. To do this, call AddNewtonsoftJson() in the initialization code, like this:

using Newtonsoft.Json.Serialization;

//rest of adding services

builder.Services.AddControllers().AddNewtonsoftJson(options =>
{
    //customize settings here. For example, change the naming strategy

    options.SerializerSettings.ContractResolver = new DefaultContractResolver()
    {
        NamingStrategy = new SnakeCaseNamingStrategy()
    };
});

//rest of init code
Code language: C# (cs)

Note: Install the Microsoft.AspNetCore.Mvc.NewtonsoftJson package if necessary.

To see this work, send a request:

GET https://localhost:12345/booksCode language: plaintext (plaintext)

It returns the following JSON with snake-cased property names, showing that it’s using Newtonsoft as configured above:

{
    "title": "Code",
    "subtitle": "The Hidden Language of Computer Hardware and Software",
    "author_name": "Charles Petzold",
    "date_first_published": "2000-10-11T00:00:00"
}Code language: JSON / JSON with Comments (json)

5 thoughts on “ASP.NET Core – Configure JSON serializer options”

  1. The first alternative approach is to maintain the property names casing globally, locate the ConfigureServices method and update it by adding the using Newtonsoft.Json.Serialization; line at the top.


    using Newtonsoft.Json.Serialization;

    public void ConfigureServices(IServiceCollection services)
    {

    services.AddMvc()
    .AddNewtonsoftJson(options =>
    options.SerializerSettings.ContractResolver =
    new DefaultContractResolver());

    // Add the Kendo UI services to the services container.
    services.AddKendo();
    }

    Reply
  2. To parse date as JValue of type JTokenType.String instead of type JTokenType.Date objects with Newtonsoft.Json use this:

    services.AddControllers()
    .AddNewtonsoftJson(options =>
    {
    options.SerializerSettings.DateParseHandling = DateParseHandling.None;
    });

    Then you can parse this string in the controller according to your needs.

    Reply
  3. Thanks for this really useful article.
    One question regarding the custom json converter (StockConverter). How can it be extended to get the details regarding the eventual deserialization exception from the controller method (for instance in the Read() method of the converter I would add a try/catch block) ? I would like to be able to send to the web client a HttpResponse with a more detailed description containing the json exception details. By default the NET implementation sends a BadRequest, but without providing any details about where the json deserialization failed.

    Reply
    • I’m glad you found this useful!

      To answer your question, if I understand correctly you want to customize the error responses when a deserialization error happens.

      First, when a deserialization exception happens, the framework uses ApiBehaviorOptions.InvalidModelStateResponseFactory to produce a 400 “problem details” response. By default, this uses ProblemDetailsClientErrorFactory.

      You can change this to use your own implementation that returns your own custom error response. There’s two ways to do this:

      1. Implement your own centralized error response factory:
      services.AddControllers().ConfigureApiBehaviorOptions(options =>
      {
          options.InvalidModelStateResponseFactory = context =>
          {
              var validationDetails = new ValidationProblemDetails(context.ModelState);

              return new BadRequestObjectResult(new
              {
                  //put other context info here
                  Errors = validationDetails.Errors
              });
          };
      });

      2. Handle error responses per-controller method:

      First, turn off the centralized handler:
      services.AddControllers().ConfigureApiBehaviorOptions(options =>
      {
          options.SuppressModelStateInvalidFilter = true;
      });

      Then in controller methods, check if ModelState is invalid and return whatever error response.
      [HttpPost()]
      public IActionResult Post(Stock stock)
      {
          if (!ModelState.IsValid)
          {
              return new BadRequestObjectResult(new
              {
                  Description = "Errors while deserializing Stock model for StocksController.Post",
                  Errors = new ValidationProblemDetails(ModelState).Errors
              });
          }
          
          

      //rest of method

      }
      (paraphrased from email conversation)

      Reply

Leave a Comment