I’ve recently been trying out the new System.Text.Json JSON Parser that is now built into .NET Core 3+, replacing NewtonSoft.Json (Sometimes called JSON.NET) as the default JSON parser in ASP.NET Core. There has been quite a few gotchas and differences between the two libraries, but none more interesting than the following piece of documentation :

During deserialization, Newtonsoft.Json does case-insensitive property name matching by default. The System.Text.Json default is case-sensitive, which gives better performance since it’s doing an exact match.

I found this interesting, especially the last line which suggests that doing exact matches by default results in much better performance.

Interestingly enough, there is also the following piece of info :

If you’re using System.Text.Json indirectly by using ASP.NET Core, you don’t need to do anything to get behavior like Newtonsoft.Json. ASP.NET Core specifies the settings for camel-casing property names and case-insensitive matching when it uses System.Text.Json.

So that essentially means when we switch to System.Text.Json in an ASP.NET Core project specifically, things will be case insensitive by default, and by extension, have slightly worse performance than forcing things to be case sensitive. By the way, for the record, I think this should be the default behaviour because in 99% of Web SPA cases, the front end will be using javascript which typically is written using camelCase, and if the backend is in C#, the properties are typically written in PascalCase.

But here’s the thing I wondered. When everyone’s out there posting benchmarks, are they benchmarking case sensitive or case insensensitive JSON parsing? In another blog post I and many others often refer back to for JSON benchmarks (https://michaelscodingspot.com/the-battle-of-c-to-json-serializers-in-net-core-3/) they are actually doing the case sensitive parsing which again, I don’t think is going to be the reality for the majority of use cases.

Let’s dig a little more!

Benchmarking

The first thing I wanted to do was create a simple benchmark to test my hypothesis. That is, does deserializing data with case insensitivity turned on slow down the deserialization process.

I’m going to use BenchmarkDotNet for this purpose. The class I want to deserialize looks like so :

public class MyClass { public int MyInteger { get; set; } public string MyString { get; set; } public List<string> MyList { get; set; } }

Now I also had an inkling of a theory this wouldn’t be as simple as first thought. I had a hunch that possibly that even when case insensitivity was turned on, if it could find an exact match first, it would attempt to use that anyway. e.g. It’s possible that the code would look something like this pseudo code :

if(propertyName == jsonProperty) { //We found the property on an exact match. }else if(propertyName.ToLower() == jsonProperty.ToLower()) { //We found it after ToLowering everything. }

With that in mind, I wanted my benchmark to test serializing both PascalCase names (So exact match), and camelCase names. My benchmark looked like so :

public class SystemTextVsJson { private readonly JsonSerializerOptions options = new JsonSerializerOptions() { PropertyNameCaseInsensitive = true }; private const string _jsonStringPascalCase = "{\"MyString\" : \"abc\", \"MyInteger\" : 123, \"MyList\" : [\"abc\", \"123\"]}"; private const string _jsonStringCamelCase = "{\"myString\" : \"abc\", \"myInteger\" : 123, \"myList\" : [\"abc\", \"123\"]}"; [Benchmark] public MyClass SystemTextCaseSensitive_Pascal() { return JsonSerializer.Deserialize<MyClass>(_jsonStringPascalCase); } [Benchmark] public MyClass SystemTextCaseInsensitive_Pascal() { return JsonSerializer.Deserialize<MyClass>(_jsonStringPascalCase, options); } [Benchmark] public MyClass SystemTextCaseSensitive_Camel() { return JsonSerializer.Deserialize<MyClass>(_jsonStringCamelCase); } [Benchmark] public MyClass SystemTextCaseInsensitive_Camel() { return JsonSerializer.Deserialize<MyClass>(_jsonStringCamelCase, options); } }

And just so we are all on the same page, the machine I’m running this on looks like :

BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18362 AMD Ryzen 7 2700X, 1 CPU, 16 logical and 8 physical cores .NET Core SDK=3.1.100

Now onto our results :

Method Mean Error StdDev SystemTextCaseSensitive_Pascal 1.511 us 0.0298 us 0.0279 us SystemTextCaseInsensitive_Pascal 1.538 us 0.0052 us 0.0049 us SystemTextCaseSensitive_Camel 1.877 us 0.0297 us 0.0277 us SystemTextCaseInsensitive_Camel 2.548 us 0.0164 us 0.0145 us

Interesting. Very interesting. So a couple of things that stick out to me immediately.

If we are using PascalCase for our property names on both ends (in the JSON and our C# class), then the case sensitivity setting doesn’t matter all too much. This proves my initial thoughts that it may try for an exact match no matter the setting as that’s likely to be faster than any string manipulation technique.

Next. Slightly of interest is that when parsing with case sensitivity turned on, when there is no match (e.g. You have screwed up the casing on one of the ends), it runs slightly slower. Not by much. But enough to be seen in the results. This is probably because it tries to do some extra “matching” if it can’t find the exact match.

Finally. Oof. Just as we thought. When we are doing case insensitive matching and our incoming data is camelCase with the class being PascalCase, the benchmark is substantially slower than exact matching. And I just want to remind you, the default for ASP.NET Core applications is case insensitive.

So, how does this actually stack up?

Benchmarking Against Newtonsoft

The interesting thing here was if we are comparing apples to apples, Newtonsoft also does case insensitive matching but it does so by default. So when we do any benchmarking against it, we should try and do so using similar settings if those settings would be considered the norm.

With that in mind, let’s do this benchmark here :

public class SystemTextVsJson { private readonly JsonSerializerOptions options = new JsonSerializerOptions() { PropertyNameCaseInsensitive = true }; private const string _jsonStringCamelCase = "{\"myString\" : \"abc\", \"myInteger\" : 123, \"myList\" : [\"abc\", \"123\"]}"; [Benchmark] public MyClass SystemTextCaseInsensitive_Camel() { return JsonSerializer.Deserialize<MyClass>(_jsonStringCamelCase, options); } [Benchmark] public MyClass NewtonSoftJson_Camel() { return Newtonsoft.Json.JsonConvert.DeserializeObject<MyClass>(_jsonStringCamelCase); } }

And the results?

Method Mean Error StdDev SystemTextCaseInsensitive_Camel 2.555 us 0.0106 us 0.0099 us NewtonSoftJson_Camel 2.852 us 0.0104 us 0.0087 us

Much much much closer. So now that we are comparing things on an even footing the performance of System.Text.Json to NewtonSoft actually isn’t that much better in terms of raw speed. But what about memory? I hear that touted a lot with the new parser.

Memory Footprint

BenchmarkDotNet gives us the ability to also profile memory. For our test, I’m going to keep the same benchmarking class but just add the MemoryDiagnoser attribute onto it.

[MemoryDiagnoser] public class SystemTextVsJson { }

And the results

Method Allocated SystemTextCaseInsensitive_Camel 408 B NewtonSoftJson_Camel 3104 B

Wow, credit where credit is due, that’s a very impressive drop. Now again I’m only testing with a very minimal JSON string, but I’m just looking to do a comparison between the two anyway.

Final Thoughts

Why did I make this post in the first place? Was it to crap all over System.Text.Json and be team JSON.NET all the way? Not at all. But I have to admit, there is some level of frustration when moving to using System.Text.Json when it doesn’t have the “features” that you are used to in JSON.NET, but it’s touted as being much faster. Then when you dig a little more in the majority of use cases (case insensitive), it’s not actually that much faster.

And I have to point out as well. That literally everytime I’ve written a benchmarking post for C# code, I’ve managed to get something wrong where I didn’t know the compiler would optimize things out etc and someone jumps in the Reddit comments to call me an idiot (Will probably happen with this one too! Feel free to drop a comment below!). So you can’t really blame people doing benchmarks across JSON parsers without realizing the implications of casing because the fact ASP.NET Core has specific defaults that hide away this fact means that you are unlikely to run into the issue all that often.

If you are in the same boat as me and trying to make the leap to System.Text.Json (Just so you stay up to date with what’s going on), I have a post sitting in my drafts around gotchas with the move. Case sensitivity is a big one but also a bunch of stuff on various defaults, custom converters, null handling etc which were all so great in JSON.NET and maybe a little less great in System.Text.Json. So watch this space!