Discover System.Text.Json (Part 1)

Posted by

The release of .NET Core 3 denotes a big shift in the handling of JSON within the .NET ecosystem: it’s the first release of ASP.NET Core that doesn’t depend on Newtonsoft’s incredibly popular Json.NET package.

Instead, .NET Core now has its own native JSON library, called System.Text.Json. I recently ported a project over to the new library and both myself and a colleague struggled to get used to it. I’ve grown so used to Json.NET’s APIs that working with anything else feels foreign.

The goal of these posts is to give you an overview of the 20% you need to know to do 80% of any work with System.Text.Json. I won’t be going into the nitty-gritty details, nor will I do an in-depth feature comparison with Newtonsoft. There are better resources for that, which I’ll point you at throughout the series.

The planned posts are the following:

  • Part 1 [this post]: Why System.Text.Json exists, the major differences with Newtonsoft, and where to find help with the new library.
  • Part 2 [coming September 1 2020]: Reading and parsing JSON documents.
  • Part 3 [coming soon]: Serializing and writing JSON documents.
  • Part 4 [coming soon]: Using POCOs and Model Binding in ASP.NET Core.
  • Part 5 [coming soon]: What to consider before using System.Text.Json in a production-grade project.

The Case for A Native Parser

As of .NET Core 2.1, Json.NET was entrenched in many of Microsoft’s development tools, including ASP.NET Core and SignalR. It never quite made sense that these components relied on an external library, albeit an open source one. In that respect, the decision to create create System.Text.Json makes sense.

The decision also aligns with Microsoft’s decision to bake in commonly used tooling like logging, dependency injection, and now JSON. It’s a fine line to draw. Include too much and you stifle the open source community’s contributions; include too little and you make it harder for developers to focus on the business problems they are trying to solve.

Serialization and deserialization are expensive operations. New features in C# like Span<T> make manipulating JSON strings more lightweight than ever before. By building a new library from scratch, the development team had the opportunity to design everything with a performance mindset — something that would have been near-impossible without causing breaking changes to Json.NET.

Put all these reasons together and what you have is a clear direction for the upcoming .NET 5: a high-performance, integrated way to handle the most commonly used data interchange format. 

Major Differences with Json.NET

Json.NET and System.Text.Json achieve identical goals in different ways. Most of these differences have pros and cons, so keep an open mind as we evaluate the impact they have on our ability to write effective code.

JObject Equivalent… Or Lack Thereof

Creating, reading, and updating JSON documents on the fly with Newtonsoft’s JObject type is a cinch. I use the type a lot, from building basic JSON documents on the fly to creating mock JSON documents for tests with its IDictionary initializer.

But System.Text.Json’s closest type, JsonDocument, isn’t writable and isn’t going to be any time soon. Requests to add a string indexer and IEnumerable support to a related type, JsonElement, have also been closed for valid design reasons. The lack of a JObject-like type was the biggest painpoint I had when trying to use System.Text.Json.

The API to create, read and update JSON documents with System.Text.Json is more low level than its Newtonsoft equivalent.

Reading a property from a JSON document is quite pleasant and feels less hacky than accessing a property with the string indexer:

var jsonnetSample = new JObject()
       { "Topic", "Json Serialization Part 1"},
       { "Part", 1 },
       { "Author", "Marc" }
var stjSample = JsonDocument.Parse(jsonnetSample.ToString()); 

var stjTopic = stjSample.RootElement.GetProperty("Topic").GetString(); // System.Text.Json
var jsonnetTopic = jsonnetSample["Topic"].ToString();                  // Json.NET

But when it comes to updating or removing a property from a document, there is no one-for-one replacement to do the following:

jsonnetSample["Topic"] = "Json Serialization Part 1";

The object model is read-only, so you need to come up with more elaborate ways to update documents on the fly like this. We’ll be looking at this more in the next post — I just wanted to give you a quick idea of what we’re dealing with.

Newtonsoft.Json Conflicts are Eliminated

The problem with .NET Core depending on an external package like Json.NET is that it’s all too easy to end up with version conflicts. As an example, .NET Core 2 depends on Newtonsoft.Json.11.0.2, but if you need another package that references a different version of Newtonsoft.Json, you could end up with some unexpected behaviours, or even break the underlying platform.

All that is a thing of the past with System.Text.Json. As the namespace implies, the new JSON library is native to .NET Core. The dependency on Newtonsoft.Json is gone entirely from ASP.NET Core, unless you explicitly want to use it.

Value Types over Reference Types

As mentioned above, many of the decisions in the design of the API were made with performance in mind. To that end, some of the types in System.Text.Json are structs instead of classes. Put simply, object allocations and deallocations are more expensive than struct allocations, reducing the overall footprint of the library.

What this means is that working with System.Text.Json’s types is more akin to working with other value types instead of the reference types widely used in Json.NET. Concretely, that means that:

  • You can’t assign null, or check for null, on any of the types defined as structs.
  • Assignment statements copy the value of a struct type variable rather than the memory reference to the value.

Finding Help When Stuck

Another challenge I ran into when getting started with System.Text.Json is finding help. I needed two types of assistance:

  • General explanations on how to use the APIs together to do something useful.
  • Specific explanations on how to do precise things, like having a required property, or deserializing to an immutable type.

I struggled mightily with the first one. There are some examples available in the official docs, but more advanced examples would be appreciated. That’s the core of what I’m trying to address in this series.

Every developer’s first instinct for how to do specific things is to check StackOverflow. There is a surprisingly small number of questions tagged with System.Text.Json (36 as of this writing). It’s normal that a new library doesn’t have hundreds of questions answered, but that means we need to look for answers elsewhwere.

.NET Core’s GitHub issues page has proven to be invaluable to resolve edge cases and find workarounds for unsupported features. It’s best to use the area-System.Text.Json label to filter the results down to the most relevant issues:

System.Text.Json Is Here To Stay

Microsoft’s latest JSON parsing library isn’t a one-for-one replacement of Json.NET, but it was a necessary step towards removing a dependency that could be a liability moving forward towards .NET 5.

The decisions made in the design of the APIs are in line with the goal of improving performance, at the cost of usability, especially with regards to the JObject type. This release feels like the minimum viable product, and I’ve no doubt that the APIs and tooling around System.Text.Json will continue to improve with future versions .NET.

Next up in the series: I’ll break down the major types you’re likely to encounter and the structure of a JSON object as represented in System.Text.Json.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s