Try the new System.Text.Json APIs

Immo Landwerth

For .NET Core 3.0, we’re shipping a brand new namespace called System.Text.Json with support for a reader/writer, a document object model (DOM), and a serializer. In this blog post, I’m telling you why we built it, how it works, and how you can try it.

We also have a video:

Getting the new JSON library

  • If you’re targeting .NET Core. Install the latest version of the .NET Core 3.0 preview. This gives you the new JSON library and the ASP.NET Core integration.
  • If you’re targeting .NET Standard or .NET Framework. Install the System.Text.Json NuGet package (make sure to include previews and install version 4.6.0-preview6.19303.8 or higher). In order to get the integration with ASP.NET Core, you must target .NET Core 3.0.

The future of JSON in .NET Core 3.0

JSON has become an essential part of virtually all modern .NET applications and in many cases even surpassed the usage of XML. However, .NET hasn’t had a (great) built-in way to deal with JSON. Instead, we’ve relied on Json.NET until now, which continues to serve the .NET ecosystem well.

We’ve decided that we needed to build a new JSON library:

  • Provide high-performance JSON APIs. We needed a new set of JSON APIs that are highly tuned for performance by using Span<T> and can process UTF-8 directly without having to transcode to UTF-16 string instances. Both aspects are critical for ASP.NET Core, where throughput is a key requirement. We considered contributing changes to Json.NET, but this was deemed close to impossible without either breaking existing Json.NET customers or compromising on the performance we could achieve. With System.Text.Json, we were able to gain 1.3x – 5x speed up, depending on the scenario (see below for more details). And we believe we can still squeeze out more.
  • Remove Json.NET dependency from ASP.NET Core. Today, ASP.NET Core has a dependency on Json.NET. While this provides a tight integration between ASP.NET Core and Json.NET, it also means the version of Json.NET is dictated by the underlying platform. However, Json.NET is frequently updated and application developers often want to — or even have to — use a specific version. Thus, we want to remove the Json.NET dependency from ASP.NET Core 3.0, so that customers can choose which version to use, without fearing they might accidentally break the underlying platform.
  • Provide an ASP.NET Core integration package for Json.NET. Json.NET has basically become the Swiss Army knife of JSON processing in .NET. It provides many options and facilities that allow customers to handle their JSON needs with ease. We don’t want to compromise on the Json.NET support customers are getting today. For example, the ability to configure the JSON serialization in ASP.NET Core via the AddJsonOptions extension method. Thus, we want to provide the Json.NET integration for ASP.NET Core as a NuGet package that developers can optionally install, so they get all the bells and whistles they get from Json.NET today. The other part of this work item is to ensure we have the right extension points so that other parties can provide similar integration packages for their JSON library of choice.

For more details on the motivation and how it relates to Json.NET, take a look at the announcement we made back in October.

Using System.Text.Json directly

For all the samples, make sure you import the following two namespaces:

Using the serializer

The System.Text.Json serializer can read and write JSON asynchronously and is optimized for UTF-8 text, making it ideal for REST API and back-end applications.

By default, we produce minified JSON. If you want to produce something that is human readable, you can pass in an instance of JsonSerializerOptions to the serializer. This is also the way you configure other settings, such as handling of comments, trailing commas, and naming policies.

Deserialization works similarly:

We also support asynchronous serialization and deserialization:

You can also use custom attributes to control serialization behavior, for example, ignoring properties and specifying the name of the property in the JSON:

We currently don’t have support for F# specific behaviors (such as discriminated unions and record types), but we plan on adding this in the future.

Using the DOM

Sometimes you don’t want to deserialize a JSON payload, but you still want structured access to its contents. For example, let’s say we have a collection of temperatures and want to average out the temperatures on Mondays:

The JsonDocument class allows you to access the individual properties and values quite easily.

Using the writer

The writer is straight forward to use:

The reader requires you to switch on the token type:

Integration with ASP.NET Core

Most use of JSON inside of ASP.NET Core is provided via the automatic serialization when accepting or returning object payloads, which in turn means that most of your application’s code is agnostic to which JSON library ASP.NET Core is using. That makes it easy to switch from one to another.

You can see the details on how you can enable the new JSON library in MVC and SignalR later on in this post.

Integration with ASP.NET Core MVC

In Preview 5, ASP.NET Core MVC added support for reading and writing JSON using System.Text.Json. Starting with Preview 6, the new JSON library is used by default for serializing and deserializing JSON payloads.

Options for the serializer can be configured using MvcOptions:

If you’d like to switch back to the previous default of using Newtonsoft.Json, do the following:

  1. Install the Microsoft.AspNetCore.Mvc.NewtonsoftJson NuGet package.
  2. In ConfigureServices() add a call to AddNewtonsoftJson()

Known issues

  • Support for OpenAPI / Swagger when using System.Text.Json is ongoing and unlikely to be available as part of the 3.0 release.

Integration with SignalR

System.Text.Json is now the default Hub Protocol used by SignalR clients and servers starting in ASP.NET Core 3.0 Preview 5.

If you’d like to switch back to the previous default of using Newtonsoft.Json, then you can do so on both the client and server.

  1. Install the Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson NuGet package.
  2. On the client add .AddNewtonsoftJsonProtocol() to the HubConnectionBuilder:
  3. On the server add .AddNewtonsoftJsonProtocol() to the AddSignalR() call:

Performance

Since this feature is heavily motivated by performance, we’d like to share some high-level performance characteristics of the new APIs.

Please keep in mind that these are based on preview builds and the final numbers will most likely differ. We’re also still tweaking default behaviors which will affect performance (for example, case sensitivity). Please note that these are micro benchmarks. Your mileage will most certainly differ, so if performance is critical for you, make sure to make your own measurements for scenarios that best represent your workload. If you encounter scenarios you’d like us to optimize further, please file a bug.

Raw System.Text.Json

Just doing micro benchmarks to compare System.Text.Json with Json.NET yields the following output:

Scenario Speed Memory
Deserialization 2x faster Parity or lower
Serialization 1.5x faster Parity or lower
Document (read-only) 3-5x faster ~Allocation free for sizes < 1 MB
Reader 2-3x faster ~Allocation free (until you materialize values)
Writer 1.3-1.6x faster ~Allocation free

 

System.Text.Json in ASP.NET Core MVC

We’ve written an ASP.NET Core app that generates data on the fly that is then serialized and deserialized from MVC controllers. We then varied the payload sizes and measured the results:

JSON deserialization (input)

Description RPS CPU (%) Memory (MB)
Newtonsoft.Json – 500 B 136,435 95 172
System.Text.Json – 500 B 167,861 94 169
Newtonsoft.Json – 2.4 KB 97,137 97 174
System.Text.Json – 2.4 KB 132,026 96 169
Newtonsoft.Json – 40 KB 7,712 88 212
System.Text.Json – 40 KB 16,625 96 193

 

JSON serialization (output)

Description RPS CPU (%) Memory (MB)
Newtonsoft.Json – 500 B 120,273 94 174
System.Text.Json – 500 B 145,631 94 173
Newtonsoft.Json – 8 KB 35,408 98 187
System.Text.Json – 8 KB 56,424 97 184
Newtonsoft.Json – 40 KB 8,416 99 202
System.Text.Json – 40 KB 14,848 98 197

 

For the most common payload sizes, System.Text.Json offers about 20% throughput increase in MVC during input and output formatting with a smaller memory footprint.

Summary

In .NET Core 3.0, we’ll ship the new System.Text.Json APIs, which provide built-in support for JSON, including reader/writer, read-only DOM, and serializer/deserializer. The primary goal was performance and we see typical speedups of up to 2x over Json.NET, but it depends on your scenario and your payload, so make sure you measure what’s important to you.

ASP.NET Core 3.0 includes support for System.Text.Json, which is enabled by default.

Give System.Text.Json a try and send us feedback!

{"happy": "coding!"}

73 comments

Discussion is closed. Login to edit/delete existing comments.

  • AnFa 0

    I currently try to evaluate whether we can switch from “Newtownsoft” to “System.Text.Json” in future. One missing functionality I found is missing supports for serializing enums to their text name (without need to write converter). Is this feature planed in some of the next releases? Thx upfront

  • Terje Bergesen 0

    It would have been nice if it worked 🙂
    I have an object with a nested object, with the nested object containing zero references to the containing object, and still, when I try to return this from an api controller, .net goes bananas, allocates 4G of memory and crashes. If I do a serialize from Newtonsoft, it works like a charm, no problems.
    I am not quite sure why this happens yet, but when I find out I’ll post some example code. I know that it is the nested object that causes the problem, since setting it to null prevents (what I assume is the circular reference from) gobbling up memory. I can only assume that we are talking about a reference from the child object to the parent object causing this, but as defined the child class contains no references to the parent class, so I have to assume it is some sort of internal references being followed.

    OK, so here’s the code that will crash the new JSON Formatter, basically rendering it completely useless at this point in time, tested with .net core 3.0 preview 8.

    public class AUser{  public string Fullname { get; set; }  public AFolder HomeFolder { get; set; }}
    public class AFolder{  public string Name { get; set; }  public IList<AFolder> SubFolders { get; set; }}

    In some code somewhere do
    var user = new AUser {  HomeFolder = new AFolder {    SubFolders = null // For good measure  }}
    var t = System.Text.Json.JsonSerializer.Serialize(user);

    • Ahson KhanMicrosoft employee 0

      I am unable to repro the issue you are seeing with the GA release of .NET Core 3.0 and 3.1 (LTS). Looks like it was a bug that was fixed. The sample code you shared outputs: {“Fullname”:null,”HomeFolder”:{“Name”:null,”SubFolders”:null}}

  • HUISHENG CHEN 0

    It does not support Dictionary. Had to fallback to Newtonsoft.Json

  • Sahana Hegde 0

    Is there a way to get the class type in JSON on serializing. This is required to identify the sub classes.

  • Robert Auer 0

    This seems like a step backwards. Traversing and looping though the JSON like its XML doesn’t appeal to me. I greatly prefer the Newtonsoft method.

    Hat tip for trying something new – but this will need a few iterations before I would consider putting it in my toolbox.

  • Shimmy Weitzhandler 0

    Can’t find a way to set up the serializer options using dependency injection.

  • Daniel Borges 0

    Why not just improve on the existing System.Runtime.Serialization.Json.JsonReaderWriterFactory?

  • Talley Ouro 0

    I did performance tests among all Json providers i use and i found that fastJson is the fastest among all.
    https://github.com/mgholam/fastJSON

  • Tarmo Pikaro 0

    See following question:

    https://stackoverflow.com/questions/58225122/serialize-deserialize-a-class-hierarchy-with-net-core-system-text-json

    And one side comment:
    Seems System.Text.Json has quite low level API. I switched to Newtonsoft JSON but will continue looking at how System.Text.Json evolves. – shadeglare Oct 31 at 13:01

    Can you please tackle with this problem ?
    Maybe it’s sorted out already, then just leave answer to stack overflow, maybe ?

Feedback usabilla icon