C# – Case sensitivity in JSON deserialization

By default Newtonsoft does case insensitive JSON deserialization and System.Text.Json does case sensitive JSON deserialization.

Case sensitivity comes into play when a JSON string is being deserialized into an object. If you’re using case sensitive deserialization, then keys in the JSON string must match type names exactly, otherwise it won’t deserialize the class/property with the mismatching casing.

This can be confusing if you’re switching from Newtonsoft to System.Text.Json, because a field that has mismatching casing will suddenly stop deserializing.

The following table shows a comparison of how case sensitivity is handled in Newtonsoft vs System.Text.Json.

Case sensitive deserializationCase insensitive deserialization
NewtonsoftWrite a custom converterDefault
System.Text.JsonDefaultPass in an option to make it case insensitive

In this article I’ll show how to do case sensitive and case insensitive deserialization using both Newtonsoft and System.Text.Json.

Example JSON with mismatching casing

I am using the following JSON:

{
  "city": "Detroit",
  "Name": "Lions",
  "Conference": "NFC",
  "Division": "North"
}

I want to deserialize this into an NFLTeam object:

var detroitLions = new NFLTeam()
{
	City = "Detroit",
	Name = "Lions",
	Conference = Conference.NFC,
	Division = Division.North,
};

Notice that the “city” key in the JSON string doesn’t match the casing of the “City” property in the NFLTeam class.

Case insensitive deserialization – using System.Text.Json

System.Text.Json supports case insensitive deserialization simply by turning on a setting.

  • using System.Text.Json;
  • using System.Text.Json.Serialization;
var json = "{\"city\": \"Detroit\",\"Name\": \"Lions\",\"Conference\": \"NFC\",\"Division\": \"North\"}";

var options = new JsonSerializerOptions();
options.PropertyNameCaseInsensitive = true;
options.Converters.Add(new JsonStringEnumConverter());

var team = JsonSerializer.Deserialize<NFLTeam>(json, options);

Console.WriteLine($"Team.City={team.City}");

Output:

Team.City=Detroit

Case insensitive deserialization – using Newtonsoft

Newtonsoft uses case insensitive deserialization by default, so this is easy:

  • using Newtonsoft.Json;
var json = "{\"city\": \"Detroit\",\"Name\": \"Lions\",\"Conference\": \"NFC\",\"Division\": \"North\"}";

var team = JsonConvert.DeserializeObject<NFLTeam>(json);

Console.WriteLine($"Team.City={team.City}");

Output:

Team.City=Detroit

Case sensitive deserialization – using System.Text.Json

System.Text.Json does case sensitive deserialization by default.

  • using System.Text.Json;
  • using System.Text.Json.Serialization;
var json = "{\"city\": \"Detroit\",\"Name\": \"Lions\",\"Conference\": \"NFC\",\"Division\": \"North\"}";

var options = new JsonSerializerOptions();
options.Converters.Add(new JsonStringEnumConverter());

var team = JsonSerializer.Deserialize<NFLTeam>(json, options);

Console.WriteLine($"Team.City={team.City}");

Output:

Team.City=

It’s null because the “city” key does not match the “City” property name, therefore it skips this property and it’s left null.

Case sensitive deserialization – using Newtonsoft

Case insensitive matching is hardcoded in Newtonsoft, and making this configurable has been an open request since 2016. I think it’s safe to say this won’t be changed any time soon.

If you require case sensitive deserialization, then I suggest using System.Text.Json since it’s already supported. However, if you must continue using Newtonsoft, then here are a few customization options.

Assuming you want this to work like System.Text.Json – where it simply ignores properties with mismatching casing – then you have two options:

  • Write a custom converter that filters out properties with mismatching casing.
  • Fork Newtonsoft and change the hardcoded case insensitive matching.

Option 1 – Write a custom converter that ignores properties with mismatching casing

To write a custom converter, you need to inherit from JsonConverter and then override three methods – CanConverter(), ReadJson(), and WriteJson(). The main focus of this custom converter is in ReadJson() – where deserialization happens.

The following custom converter filters out properties with mismatching casing.

Note: This is not recursive, so it only works on top-level properties. You can use this as a starting point, and add recursion for more complex cases if you need to.

  • using Newtonsoft.Json;
  • using Newtonsoft.Json.Linq;
public class CaseSensitiveDeserializer : JsonConverter
{
	public override bool CanConvert(Type objectType)
	{
		return true;
	}

	public override object ReadJson(JsonReader reader, Type objectType,
		object existingValue, JsonSerializer serializer)
	{
		if (reader.TokenType == JsonToken.Null)
			return null;

		JObject target = new JObject();

		foreach(JProperty property in JToken.Load(reader).Children())
		{
			if(objectType.GetProperty(property.Name) != null)
			{
				target.Add(property);
			}
		}

		return target.ToObject(objectType);
	}

	public override void WriteJson(JsonWriter writer, object value,
		JsonSerializer serializer)
	{
		JObject o = (JObject)JToken.FromObject(value);
		o.WriteTo(writer);
	}
}

Now use the custom converter by passing it in during deserialization:

  • using Newtonsoft.Json;
var json = "{\"city\": \"Detroit\",\"Name\": \"Lions\",\"Conference\": \"AFC\",\"Division\": \"North\"}";

var team = JsonConvert.DeserializeObject<NFLTeam>(json, new CaseSensitiveDeserializer());

Console.WriteLine($"Team.City={team.City}");

Output:

Team.City=

The “city” key is skipped, because there’s no matching property, therefore the City property is left null.

Option 2 – Fork the Newtonsoft repository and change it to do case sensitive matching

Newtonsoft is open source. Therefore you can fork the Newtonsoft repository from GitHub, and make the specific change you need.

In this case, you’ll need to change the method that hardcodes it to do case sensitive matching. The method you’ll want to change is JsonPropertyCollection.GetClosestMatchProperty().

public JsonProperty? GetClosestMatchProperty(string propertyName)
{
	JsonProperty? property = GetProperty(propertyName, StringComparison.Ordinal);
	if (property == null)
	{
		property = GetProperty(propertyName, StringComparison.OrdinalIgnoreCase);
	}

	return property;
}

After you’ve made this change, you’ll need to deal with generating a nuget package, and updating your own project to use this package.

Leave a Comment