Case Sensitivity with System.Text.Json

One of the new features that come with .NET Core 3 is the new System.Text.Json serialiser. It provides the native approach to perform JSON serialisation and deserialisation without the need to install third party package like Newtonsoft’s Json.NET. It is a new player in the field which I had the chance to play around with in the Split Our Bill project.

The API gateway in the project serve as the bridge between frontend clients and services. It has to inevitably deal with data that is being transferred to and fro in JSON format. The typical scenario is getting data from clients, serialise it to JSON string and pass it on to services through HTTP requests, awaits responses from services, and finally deserialise it for further process.

Below is the code which uses the new HttpClientFactory to create a HttpClient and send a GET request to obtain some data from backend service:

public async Task<IList<UserSimpleResponse>> GetUsers()
{
  var client = _clientFactory.CreateClient("UserService");
  var response = await client.GetAsync("users");
  response.EnsureSuccessStatusCode();
  var users = JsonSerializer.Deserialize<IList<User>>(await response.Content.ReadAsStringAsync());
  return _mapper.Map<IList<UserSimpleResponse>>(users);
}

At first glance, it seems to be simple and straightforward. However, when the method is run, the returned value is null:

[
  {
    "id": "00000000-0000-0000-0000-000000000000",
    "username" : null,
    "emailAddress": null
  }
]

Why was it not able to show value even though the HTTP response status code is 200 (OK)? The reason is the default case sensitivity behaviour of System.Text.Json. When no additional option is passed to the deserialiser method, deserialisation is case sensitive and will not be able to process any corresponding properties that has different case system (camel case and pascal case). When the content of the HTTP response is of camel case (JSON convention) and the matching property to be mapped to uses pascal case (C# convention), the deserialiser doesn’t work and thus returns the default null value for each data type.

To resolve this issue, I had to include attributes to tell the JSON deserialiser to be case insensitive:

public class User
{
  [JsonPropertyName("id")]
  public Guid Id { get; set; }
  [JsonPropertyName("username")]
  public string Username { get; set; }
  [JsonPropertyName("emailAddress")]
  public string EmailAddress { get; set; }
}

Alternatively, I could also pass an option object to the JSON deserialiser to tell it to process the content and ignore the case differences. This method ensures we don’t pollute the domain models.

var users = JsonSerializer.Deserialize<IList<User>>(await response.Content.ReadAsStringAsync(), options: new JsonSerializerOptions()
{
  PropertyNameCaseInsensitive = true
});

How about serialisation?

Apparently serialisation doesn’t have such issue since the serialised JSON string is passed on to controller of other services which can process the case difference.

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 )

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