C# – Deserialize JSON using different property names

When JSON property names and class property names are different, and you can’t just change the names to match, you have three options:

  • Use the JsonPropertyName attribute.
  • Use a naming policy (built-in or custom).
  • A combination of these two. In other words, use JsonPropertyName for special cases that your naming policy doesn’t handle.

These options affect both deserialization and serialization.

Let’s say you have the following JSON with camel-cased property names:

{
  "title": "Code",
  "subtitle": "The Hidden Language of Computer Hardware and Software",
  "authorName": "Charles Petzold",
  "dateFirstPublished": "2000-10-11T00:00:00"
}Code language: JSON / JSON with Comments (json)

Here’s an example of using the JsonPropertyName attribute:

using System.Text.Json.Serialization;

public class Book
{
	[JsonPropertyName("title")]
	public string Title { get; set; }
	
	[JsonPropertyName("subtitle")]
	public string Subtitle { get; set; }
	
	[JsonPropertyName("authorName")]
	public string AuthorName { get; set; }
	
	[JsonPropertyName("dateFirstPublished")]
	public DateTime DateFirstPublished { get; set; }
}
Code language: C# (cs)

Note: The Newtonsoft equivalent is [JsonProperty(“title”)]

Alternatively, you can use a naming policy to change ALL property names:

using System.Text.Json;

var options = new JsonSerializerOptions()
{
	PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
var codeBook = JsonSerializer.Deserialize<Book>(bookJson, options);
Code language: C# (cs)

JsonNamingPolicy.CamelCase is the only built-in naming policy right now. When you don’t specify a naming policy, the default behavior is to use property names as is. If this isn’t sufficient, you can create a custom naming policy or use a third-party library, which I’ll explain below.

Note: ASP.NET Core defaults to using JsonSerializerDefaults.Web, which includes JsonNamingPolicy.CamelCase.

Custom naming policy

To create your own custom naming policy, subclass JsonNamingPolicy and override the ConvertName() method:

using System.Text.Json;

public class CustomNamingPolicy : JsonNamingPolicy
{
	public override string ConvertName(string name)
	{
		//TODO: Implement this to return the JSON name to use for the passed in property name
		//Example: "BookName" -> "Title"

		return name;
	}
}
Code language: C# (cs)

To use your custom naming policy, set it in JsonSerializerOptions:

using System.Text.Json;

var options = new JsonSerializerOptions()
{
	PropertyNamingPolicy = new CustomNamingPolicy()
};
var codeBook = JsonSerializer.Deserialize<Book>(bookJson, options);
Code language: C# (cs)

Implementing ConvertName()

Given a class property name during (de)serialization, ConvertName() needs to output the JSON property name to use.

There are two main ways to implement this:

  • Name mappings with a dictionary.
  • Algorithmic name transformation.

If needed, do a hybrid approach. Start with a simple algorithm that works for most cases and use a dictionary (or apply the JsonPropertyName attribute) to handle your specific edge cases.

Here’s examples of these two approaches.

Example – Name mappings with a dictionary

Let’s say you have the following JSON with property names that are completely different from your class property names:

{
  "name": "Code",
  "sub": "The Hidden Language of Computer Hardware and Software",
  "auth-name": "Charles Petzold",
  "pub-date": "2000-10-11T00:00:00"
}Code language: JSON / JSON with Comments (json)

Here’s a custom naming policy that initializes a dictionary with these hardcoded name mappings:

using System.Text.Json;

public class CustomNamingPolicy : JsonNamingPolicy
{
	private readonly Dictionary<string, string> NameMapping = new Dictionary<string, string>()
	{
		[nameof(Book.AuthorName)] = "auth-name",
		[nameof(Book.DateFirstPublished)] = "pub-date",
		[nameof(Book.Title)] = "name",
		[nameof(Book.Subtitle)] = "sub"
	}; 

	public override string ConvertName(string name)
	{
		return NameMapping.GetValueOrDefault(name, name);
	}
}
Code language: C# (cs)

In ConvertName(), it uses Dictionary.GetValueOrDefault() to either get the mapped value for the name, or returns the name itself if there’s no mapping.

This is a nice, flexible option. You can hardcode the mappings or load them in from a config file (or some other external source). Since this allows you to extend your classes without directly modifying them, it adheres to the Open-Closed Principle.

The main downside is ConvertName() is only given property names. It’s not given any context information whatsoever, such as the class name. If you have two properties with the same name that need to be mapped to different JSON properties (ex: Person.Name -> “name” and Employee.Name -> “employeeName”), you’d have to use the JsonPropertyName attribute on one of the properties.

Example – Algorithmically converting to simple snake case

If the JSON property names follow some naming pattern, then you can implement an algorithmic approach. For example, let’s say you have the following JSON that’s using snake casing (ex: author_name):

{
  "title": "Code",
  "subtitle": "The Hidden Language of Computer Hardware and Software",
  "author_name": "Charles Petzold",
  "date_first_published": "2000-10-11T00:00:00"
}Code language: JSON / JSON with Comments (json)

And you want to map these properties to the following class, which is using pascal casing:

public class Book
{
	public string Title { get; set; }
	public string Subtitle { get; set; }
	public string AuthorName { get; set; }
	public DateTime DateFirstPublished { get; set; }
}
Code language: C# (cs)

Here’s a simple snake case naming policy:

using System.Text.Json;

public class SnakeCasingPolicy : JsonNamingPolicy
{
	public override string ConvertName(string name)
	{
		StringBuilder sb = new StringBuilder();

		bool lastWasLower = false;
		foreach (var c in name)
		{
			//Insert a _ when a lowercase is followed by an uppercase
			if (lastWasLower && char.IsUpper(c))
				sb.Append('_');

			lastWasLower = char.IsLower(c);

			sb.Append(char.ToLower(c));
		}

		return sb.ToString();
	}
}
Code language: C# (cs)

Note: This was tested on the names shown in the JSON / class shown above.

I recommend only solving your specific problem and keeping your algorithm as simple as possible. You have the option of hardcoding your specific edge cases in a dictionary / JsonPropertyName instead of complicating your algorithm.

Snake case naming policy

System.Text.Json doesn’t have built-in snake (ex: author_name) or kebab casing (ex: author-name). It’s possible they’ll add these in the future. This is a problem if you need it right now and don’t want to use the JsonPropertyName attribute.

Let’s say you have the following JSON with snake casing:

{
  "title": "Code",
  "subtitle": "The Hidden Language of Computer Hardware and Software",
  "author_name": "Charles Petzold",
  "date_first_published": "2000-10-11T00:00:00"
}Code language: JSON / JSON with Comments (json)

Here are your options:

I’ll show examples of how to deserialize the snake-cased JSON shown above using third party libraries.

System.Text.Json with a third-party snake case naming policy

First, install the JorgeSerrano.Json.JsonSnakeCaseNamingPolicy package:

Install-Package JorgeSerrano.Json.JsonSnakeCaseNamingPolicy
Code language: PowerShell (powershell)

Note: This is using the Package Manager in Visual Studio.

Now use it by setting it in the options:

using System.Text.Json;
using JorgeSerrano.Json;

var options = new JsonSerializerOptions()
{
	PropertyNamingPolicy = new JsonSnakeCaseNamingPolicy()
};
var codeBook = JsonSerializer.Deserialize<Book>(bookJson, options);

Console.WriteLine($"{codeBook.AuthorName} wrote the book entitled {codeBook.Title}");
Code language: C# (cs)

This outputs the following:

Charles Petzold wrote the book entitled CodeCode language: plaintext (plaintext)

Newtonsoft snake case naming strategy

To change the naming strategy in Newtonsoft, set ContractResolver.NamingStrategy in the settings. Here’s an example of using the snake case naming strategy:

using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

var settings = new JsonSerializerSettings
{
	ContractResolver = new DefaultContractResolver()
	{
		NamingStrategy = new SnakeCaseNamingStrategy()
	}
};
var codeBook = JsonConvert.DeserializeObject<Book>(bookJson, settings);

Console.WriteLine($"I like the book called {codeBook.Title} by {codeBook.AuthorName}");
Code language: C# (cs)

Note: System.Text.Json uses “naming policy”, while Newtonsoft uses “naming strategy.”

This outputs the following:

I like the book called Code by Charles PetzoldCode language: plaintext (plaintext)