Search code examples
c#jsonasp.net-coresystem.text.jsoncamelcasing

ASP.NET Core - Property VALUE to camelCase in JSON serialization


I have a SQL Tables containing definition of dynamic grids (I'm using AGGrid library for Angular). Example:

TB_AGGrid_Definitions

ID GridKey
1 TestGrid

TB_AGGrid_Columns

ID GridId Field
1 1 FirstColumn
2 1 SecondColumn

I'm using ASP.NET Core with EF Core for creating backend webapi service for my Angular Web application. I have a controller which can retrieve grid information.

Example JSON

{
   "grid":{
      "id":1,
      "gridKey":"TestGrid",
      "columns":[
         {
            "id":1,
            "gridId":1,
            "field":"FirstColumn"
         },
         {
            "id":2,
            "gridId":1,
            "field":"SecondColumn"
         }
      ]
   }
}

I just would like to transform the value of Field property in camelCase using the same alghoritm of JsonSerializer. I need to do this for binding properly the objects and the grid column definition which is case-sensitive. If I send to AGGrid FirstColumn as a field, it won't work with an object that has property named firstColumn (which will be default behavior after default serialization).

I have fields like:

"IDTabella" --\> "idTabella"

"IDDTUltimoCommission" --\> "idDtUltimoCommission"

"NDGCliente" --\> "ndgCliente"

Automatic serialization works perfectly, but I can't replicate the same result with my own function.

Expected JSON

{
   "grid":{
      "id":1,
      "gridKey":"TestGrid",
      "columns":[
         {
            "id":1,
            "gridId":1,
            "field":"firstColumn"
         },
         {
            "id":2,
            "gridId":1,
            "field":"secondColumn"
         }
      ]
   }
}

Solution

  • As you specifically asked for the camelCasing-algorithm that the JsonSerializer uses in .NET. Here is the implementation of that taken from the official open source repository for the .NET runtime on Github (See Github - dotnet/runtime).

    Implementation on current main branch with the last commit having the hash 3138a80 which is already after .NET 8 was released.

    Go to Github

    // Licensed to the .NET Foundation under one or more agreements.
    // The .NET Foundation licenses this file to you under the MIT license.
    
    namespace System.Text.Json
    {
        internal sealed class JsonCamelCaseNamingPolicy : JsonNamingPolicy
        {
            public override string ConvertName(string name)
            {
                if (string.IsNullOrEmpty(name) || !char.IsUpper(name[0]))
                {
                    return name;
                }
    
    #if NETCOREAPP
                return string.Create(name.Length, name, (chars, name) =>
                {
                    name.CopyTo(chars);
                    FixCasing(chars);
                });
    #else
                char[] chars = name.ToCharArray();
                FixCasing(chars);
                return new string(chars);
    #endif
            }
    
            private static void FixCasing(Span<char> chars)
            {
                for (int i = 0; i < chars.Length; i++)
                {
                    if (i == 1 && !char.IsUpper(chars[i]))
                    {
                        break;
                    }
    
                    bool hasNext = (i + 1 < chars.Length);
    
                    // Stop when next char is already lowercase.
                    if (i > 0 && hasNext && !char.IsUpper(chars[i + 1]))
                    {
                        // If the next char is a space, lowercase current char before exiting.
                        if (chars[i + 1] == ' ')
                        {
                            chars[i] = char.ToLowerInvariant(chars[i]);
                        }
    
                        break;
                    }
    
                    chars[i] = char.ToLowerInvariant(chars[i]);
                }
            }
        }
    }
    

    As one would imagine it's quite well implemented with performance in mind i. e. avoiding heap allocations mainly by using Span<T> where necessary and usage of some methods you would typically only use when you really care about performance (like string.Create() here).