1

In our web API we have legacy entities that have abbreviated names. We are also using code generation via T4 templates, where we like to keep things simple and predictable:

  • Template creates C# classes and capitalize the first character of the properties
  • Template creates Angular code and lowercase the first character of the properties

The JSON response from the web API however does some magic on deciding which letters to convert to lowercase, instead of only the expected first character.

For example, when starting a new default web API in Visual Studio and adding two properties to the WeatherForecast.cs:

public string TSTValue { get; set; }
public string TSTVL1 { get; set; }

The result is:

[
  {
    ...
    "tstValue":null,
    "tstvL1":null
  },
  {
    ...
    "tstValue":null,
    "tstvL1":null
  }
]

The expected/desired output would be a result with the camelcase property names:

[
  {
    ...
    "tSTValue":null,
    "tSTVL1":null
  },
  {
    ...
    "tSTValue":null,
    "tSTVL1":null
  }
]

How can the magic behavior be overridden?

JeroenW
  • 753
  • 6
  • 16
  • Are you using `System.Text.Json` in your project? – Fei Han Sep 03 '20 at 10:17
  • If you are using System.Text.Json. You can use the [JsonPropertyName("")] attribute in your class [JsonPropertyName("tSTValue")] public string TSTValue { get; set; } otherwise your could look into the JsonNamingPolicy.ConvertName(String) method to create your own custom conversion method – Kwiksilver Sep 03 '20 at 12:27
  • @FeiHan: Yes, we're using `System.Text.Json`. – JeroenW Sep 03 '20 at 12:33
  • Then do see https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-how-to#customize-json-names-and-values – Kwiksilver Sep 03 '20 at 12:43
  • @Kwiksilver: I prefer to keep everything as clean as possible, no attributes. I will have a look at your link, thanks! – JeroenW Sep 03 '20 at 13:05

1 Answers1

2

Firstly the behavior you describe is intended. You can view the source code on Github https://github.com/dotnet/runtime/blob/master/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonCamelCaseNamingPolicy.cs Thankfully the name serialization can be customized with relative ease.

Since you specified that you don't want to use attributes you can create your own JsonNamingPolicy which can be used to serialize the field names in anyway you see fit.

Below I have created a basic class which ensures that the first character is lower case and leaves the rest of the field string alone. ConvertName(string name) will be called for each field in the serialized class. In your example that is TSTValue and TSTVL1

public class JsonFirstCharToLowerNamingPolicy : JsonNamingPolicy
{
    public override string ConvertName(string name)
    {
        // if the string is empty or the first character is already lowercase just return as is
        if (string.IsNullOrEmpty(name) || char.IsLower(name[0]))
            return name;

        char[] chars = name.ToCharArray(); // get a list of chars
        chars[0] = char.ToLowerInvariant(chars[0]); // make the first character lower case 

        // leave the rest of the characters alone or do more processing on it?

        return new string(chars); // return the altered string
    }
}

To use the class you would simply just have to set the JsonSerializerOptions of the JsonSerializerOptions as shown below.

var serializeOptions = new JsonSerializerOptions
{
    PropertyNamingPolicy = new JsonFirstCharToLowerNamingPolicy(),
    WriteIndented = true
};
var jsonString = JsonSerializer.Serialize(weatherForecast, serializeOptions);
Kwiksilver
  • 775
  • 4
  • 13
  • Thanks! I prefer to keep the response of my web API strongly typed and wanted to keep the solution as generic/central as possible. So I changed our implementation of the T4 code generation: all classes and their properties are now generated and cased using a copied version of the `JsonCamelCaseNamingPolicy.ConvertName()` function, which works great! Customizable `JsonSerializerOptions` will be available in .NET 5: https://github.com/dotnet/runtime/issues/31094#issuecomment-567232806 – JeroenW Sep 08 '20 at 09:30