In this article, we are going to learn why and how to customize property names in JSON. First, we will look at the default behavior to find out why we might need to customize our JSON output. After that, we will implement a custom naming policy.
Let’s take a look.
The Default Serialization Behavior
The System.Text.Json.JsonSerializer
class has a Serialize()
method that creates a JSON file from the class we provide. By default, the Serialize()
method outputs the keys as defined in the class.
Let’s see that in action, but with a twist, we start one of the properties with a lowercase letter to see how it affects the output:
public class Person { public string? GivenName { get; set; } public string? surName { get; set; } } var person = new Person() { GivenName = "Name1", surName = "Surname1" }; var jsonString = JsonSerializer.Serialize(person); Console.WriteLine(jsonString);
Here we create a simple Person
object, serialize it, and write the output to the console:
{"GivenName":"Name1","surName":"Surname1"}
As we can see, the output follows the class definition, where the property name determines the JSON key name. Note the key “surName” has a lowercase “s” the same as the property from the Person
class.
What if we need to talk to an API or service defined using a different language, and therefore need to provide a JSON file using a different naming policy? Well, that’s why we can create custom naming policies.
Implement a Custom Naming Policy
In today’s interconnected world, APIs and web services are in multiple languages. Sometimes our C# backend talks to a C# frontend, whereas, other times we connect to services or APIs written in JavaScript, Java, or another language. Any time we receive or send data to those services we need to ensure we format that data so both ends can understand it.
Before we implement a custom naming policy let’s take a moment to review some common naming conventions.
What Is a Naming Convention?
It’s a common practice for formatting the names of variables, types, methods, and anything else in a language. So, let’s look at some of the different naming conventions:
- flatcase – all lowercase
- UPPERCASE – all uppercase
- camelCaseConvention – the first letter of the first word is lowercase, other first letters are uppercase
- PascalCaseConvention – the first letter of each word is uppercase
- snake_case_convention – an underscore (_) character between each word
- kebab-case-convention – a hyphen (-) character between each word
We can also mix and match conventions, for example, Kebab-Pascal-Case-Convention and Snake_Pascal_Case_Convention exist. Also, naming conventions have multiple names, for example, UPPERCASE goes by CONSTANTCASE, or even SCREAMINGCASE.
An important thing to note is that except for naming conventions such as the flatcase and UPPERCASE, we have a way to differentiate the start of a new word. This is an important feature of naming conventions and plays a significant role in implementing a custom naming policy.
Implement a JsonNamingPolicy Class
We create a custom naming policy by implementing the abstract JsonNamingPolicy
class, which has a few built-in static properties for common naming policies that we will discuss later. For our JsonNamingPolicy
class we override the abstract ConvertName()
method to implement our custom naming policy.
Let’s keep our examples simple and implement “camelCase” and “node/separator” policies. The “camelCase” policy is in common use on the internet, and the “node/separator” policy is fictional, however, it showcases manipulating our properties at every new word.
Let’s look at the “camelCase” implementation:
public class CamelCasePolicy : JsonNamingPolicy { public override string ConvertName(string name) { return char.IsUpper(name[0]) ? char.ToLower(name[0]) + name.Substring(1) : name; } }
We look at the first character and lowercase it if needed; otherwise, we return the parameter unchanged.
Next, let’s look at the “node/separator” implementation:
public class NodeSeparatorPolicy : JsonNamingPolicy { public override string ConvertName(string name) { if (name is null) { throw new ArgumentNullException(nameof(name)); } var sb = new StringBuilder(); sb.Append(char.ToLower(name[0])); for (int i = 1; i < name.Length; i++) { if (char.IsUpper(name[i])) { sb.Append($"/{char.ToLower(name[i])}"); } else { sb.Append(name[i]); } } return sb.ToString(); } }
First, we test for a null value, then we lowercase the first letter and add that character to a StringBuilder. From there we loop through the rest of the characters and anytime we find an uppercase character we add a forward slash (/) and a lowercase version of the character to the StringBuilder
. Now that we have our custom naming policies let’s see them in action.
Using Custom Naming Policies
After creating our custom naming policies we should use them. So, to do that we need to create a JsonSerializerOptions
class and assign a new instance of our policy to the PropertyNamingPolicy
property.
Let’s see that in action for both the CamelCasePolicy
and the NodeSeparatorPolicy
:
var person = new Person() { GivenName = "Name1", surName = "Surname1" }; var camelCaseOptions = new JsonSerializerOptions() { PropertyNamingPolicy = new CamelCasePolicy() }; jsonString = JsonSerializer.Serialize(person, camelCaseOptions); Console.WriteLine(jsonString); var nodeOptions = new JsonSerializerOptions() { PropertyNamingPolicy = new NodeSeparatorPolicy() }; jsonString = JsonSerializer.Serialize(person, nodeOptions); Console.WriteLine(jsonString);
In our example, we instantiate a person
object and serialize it with our policies resulting in this output:
{"givenName":"Name1","surName":"Surname1"} {"given/name":"Name1","sur/name":"Surname1"}
The first output uses the CamelCasePolicy
and the second uses the NodeSeparatorPolicy
. That’s all we need to do.
New Features in .NET 8 JSON Naming Policy
As of the writing of this article, Microsoft released .NET 8 which adds new, popular naming conventions to the JsonNamingPolicy
class. If we look at the Microsoft documentation we see we have five static properties:
- CamelCase provides a “camelCase” policy (in .NET Core 3.0 and higher)
- KebabCaseLower provides a “kebab-case-lower” policy (new in .NET 8)
- KebabCaseUpper provides a “Kebab-Case-Upper” policy (new in .NET 8)
- SnakeCaseLower provides a “snake_case_lower” policy (new in .NET 8)
- SnakeCaseUpper provides a “Snake_Case_Upper” policy (new in .NET 8)
The kebab cases use a dash (-) between words and have upper- and lowercase variations, whereas the snake cases use an underscore (_) between words and also have upper- and lowercase variations.
Since these are static properties we can assign them directly to the PropertyNamingPolicy
property:
var snakeCaseLowerPolicy = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower };
If we use this with our person
object from earlier, we see the properties formatted as snake-case:
{"given_name":"Name1","sur_name":"Surname1"}
Conclusion
As developers, we often need to send data to APIs and services written in other languages, meaning we need to format our JSON data to meet the needs of the receiving API or service. We achieve this by using built-in or creating custom naming policies. In this article, we discovered how easy it is to create our policies.