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.

To download the source code for this article, you can visit our GitHub repository.

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.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

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.

To read more about JSON and how to work with it in C#, you can visit this list of articles.

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.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!