Switching to System.Text.Json in .NET Core 3.0? Not so fast.

One key scenario is still not covered by the new APIs.

Robert McLaws
CloudNimble
2 min readAug 22, 2019

--

When I heard about the new System.Text.Json APIs, I was intrigued. I’m a huge fan of Newtonsoft.Json, but the improvements in Pipes in .NET Core 2.2 meant there could be opportunity for some nice performance gains.

I was really glad that Microsoft decided to do a usability study on their new APIs to make sure they weren’t too difficult to use. It turns out, they were. So in Preview 8, the team did a bunch of work cleaning up the APIs, and making them more predictable (AKA matching what people were used to with Newtonsoft.Json).

The changes are great, and the new API surface is clean and predictable. But when I went to upgrade Azure DevOps Buddy to Preview 8, I ran into some issues around a key scenario.

Now, the model binder in MVC/WebApi is quite annoying, in that the following code doesn’t work when you POST a JSON object to an API.

public async Task<bool> SomeMethod([FromBody] SomeObject data){
}

in order for this to work, you have to create the object to POST like this:

var request = new { 
data: {
property1: "test1",
property2: "test2"
}
};
var result = $ajax.post("someurl", request);

I don’t really like forcing the end developer to have to name the object like that, so I often use this pattern instead:

public async Task<bool> SomeMethod([FromBody] JObject data)
{
var model = data.ToObject<SomeObject>();
}

As far as I can tell (and I could be wrong about this, so please let me know if I am, and I’ll update the post), there is no equivalent to JObject in System.Text.Json. JsonDocument doesn’t have an empty constructor, so it can’t be used during model binding. I suppose one could create a custom model binder, but the effort didn’t seem worth the payoff.

Fortunately the APIs are not mutually-exclusive, you can use BOTH in your app, if you add the Microsoft.AspNetCore.Mvc.NewtonsoftJson NuGet package to your project, and then register them in Startup.cs like this:

public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.AddNewtonsoftJson(options =>
options.SerializerSettings.ContractResolver = new
CamelCasePropertyNamesContractResolver())
.AddJsonOptions(options => options.JsonSerializerOptions.PropertyNameCaseInsensitive = true);

Then the desired pattern will continue to work.

That’s it! If I find a better pattern, I’ll be sure to update this post. Happy Coding!

--

--

Robert McLaws
CloudNimble

Founder & CEO of BurnRate (@BurnRate_io). Serial entrepreneur. Former Microsoft MVP. @dotnetfdn member. Helping founders build amazing businesses.