How to avoid JSON deserialisation issues with .NET 6 Web APIs

It’s been a while since I’ve coded more than a few lines of C# and in that time Microsoft has gone from .NET Core 3.1 to .NET 5 and now 6.

I am currently working on an IoT gateway project and I thought I would use the new minimal APIs in .NET 6 to build the API for the gateway. The gateway accepts a POST from a device which contains JSON in the body. I wanted to serialise this into a C# object so I could do some gateway processing before sending the data upstream to Azure.

The problem

As a long-time .NET user I immediately reached for JSON.net (aka Newtonsoft.Json) and imported the nuget package into my project. I had a sample of the device JSON and utilised a VS Code extension to create a C# class based on the JSON document.

I updated one backing property to be more readable (SensorName) and left the JsonProperty to match the incoming document property (Geo) I wanted mapped to this field.

// Incorrect sample

using Newtonsoft.Json;

public class SensorData
{
   [JsonProperty("SensorId")]
   public string SensorId { get; set; }

   [JsonProperty("DateTime")]
   public string DateTime { get; set; }

   [JsonProperty("Geo")]
   public string SensorName { get; set; }
}

Job almost done I thought!

I headed over to my Controller class in my ASP.NET Web API and created the method to accept the POST and data.

[HttpPost]
public async Task<string> Post(SensorData data)
{
   // Do some work
   return "OK";
}

Now that the code was ready for testing I ran the code in the debugger and pushed a test call to this API endpoint and immediately received this response:

The field SensorName is required.

Wait, this is the backing field and not the JSON property – this isn’t the response I was expecting! I also noticed that other fields held their default values (such as 0 for an int). Not great!

I spent quite some time hunting down the cause and it looks like this is due to the .NET shift towards using it’s own in-built JSON libraries, the reasons for which are documented from the original launch in 2019.

The fix

It turns out that the fix is to forget the Newtonsoft.Json library completely and rely only on System.Text.Json instead. You’ll notice that the property attribute is also named differently (JsonPropertyName), so you need to update that as well.

// Correct sample

using using System.Text.Json.Serialization;

public class SensorData
{
   [JsonPropertyName("SensorId")]
   public string SensorId { get; set; }

   [JsonPropertyName("DateTime")]
   public string DateTime { get; set; }

   [JsonPropertyName("Geo")]
   public string SensorName { get; set; }
}

After making this change the deserialisation of the request body worked as expected and all fields were populated and I was on my merry way.

I thought I’d post here in case anyone else comes across this issue because there was no ‘aha’ moment on Stack Overflow or elsewhere, so I hope this is yours!

Happy days! 😎

2 thoughts on “How to avoid JSON deserialisation issues with .NET 6 Web APIs

  1. This fix unfortunately was the wrong choice for our application. I’ll tell you why below. But the alternative fix is, in the startup file `ConfigureServices()` function, add `services.AddNewtonsoftJson();` This will let you use Newtonsoft as the serializer, which allows all the Newtonsoft `JsonProperty` Attributes to work as they did before.

    Why do I currently recommend the alternative?

    Newtonsoft is rather mature and handles a lot of things automatically, while System.Text.Json.Serialization is not as mature and doesn’t handle those things. It’s mostly edge cases. Things that slip by and you’re not going see immediately, and you’re not going to be able to find all of.

    In our case, our application reached production with this fix, only to have to be rolled back when we discovered that nulls weren’t handled properly.

    The frontend web application doesn’t sanitize the data. It uses it as close to the form inputs as possible. So a dropdown list doesn’t have `null` as the value for unselected. It instead has `””` (empty string). Meanwhile the application is expecting a number. Newtonsoft can automatically translate numbers and strings. It can figure out that a blank string means `null` for a number field. The built-in cannot. The end result was that all of our dropdown lists were suddenly required where they were not required before. We don’t at this time know what all else changed due to this.

    As a bonus: if you haven’t already done all the legwork to do the abandon-newtonsoft fix, you’ve got a one-line fix ahead of you, instead of every single property on every single model in your entire application.

    1. Thanks for highlighting the challenges you faced in moving to use of System.Text.Json.

      While I can’t be sure of all the edge cases you mention, you do have control over how the System.Text.Json serializer works through items such as JsonSerializerOptions and implementation of custom JsonConverters. Granted this is different to the existing Newtonsoft library which is very mature and battle-hardened and may present more work than necessary for something that should “just work” 🙂

Leave a Reply

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

WordPress.com Logo

You are commenting using your WordPress.com 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