Search code examples
c#serializationjson.netado.net

Turn off camelcase JSON serialization for certain classes


Hope that someone can help me with this seemingly simple problem. In short, I want to use camel case for all property names, except when the property is a DataTable. In that case, I want the column names to be unchanged when serialized.

I have a rather deep object structure that I serialize to JSON, using JSON.NET. I want the property names to be camel case, and I dont want to put attributes on every class, so I use the following code:

public static class JsonHelper
{
    private static DefaultContractResolver _contractResolver = new DefaultContractResolver()
    {
        NamingStrategy = new CamelCaseNamingStrategy()
    };

    public static string SerializeObjectToJson(object obj)
    {
        string json = JsonConvert.SerializeObject(obj, new JsonSerializerSettings
        {
            ContractResolver = _contractResolver
        });

        return json;
    }
}
...
string json = JsonHelper.SerializeObjectToJson(myDataDTO);

That works fine. However, I also have ADO.NET DataTable properties, and in that case I want to keep the case of the column names unchanged.

Simplified example here:

public class MySampleDTO
{
    public Object Data { get; init; }
    public int SomeInteger { get; init; }
}
...
// Get table with columns "Period", "Sales" and "Expenses"
DataTable myDataTable = ...
MySampleDTO dto = new MySampleDTO() {
   Data = myDataTable,
   SomeInteger = 3
}
string json = JsonHelper.SerializeObjectToJson(dto);

The resulting JSON becomes:

{
  "data": [
    {
      "period": "2004",
      "sales": 400,
      "expenses": 700
    },
    {
      "period": "2005",
      "sales": 500,
      "expenses": 900
    },
    {
      "period": "2006",
      "sales": 720,
      "expenses": 420
    }
  ],
  "someInteger": 3
}

Obviously, the column names of the data table are serialized as camelcase which is not what I want (but I want "SomeInteger" to be camel case)

Any ideas how I can solve this?


Solution

  • Json.NET uses a DataTableConverter to convert a DataTable to and from JSON.

    This one shares the same serializer with all other converters that are being used for the (de)serialization of an object, in this case one configured with a camel case naming strategy.

    In order to apply a different naming strategy for a DataTable, you can implement a custom DataTableConverter in which you configure the serializer to use a different IContractResolver/DefaultContractResolver without the camel case naming strategy while (de)serializing the DataTable.

    Alternativelly you might set up a whole different serializer that you pass to base.WriteJson and base.ReadJson.


    public class CustomDataTableConverter : DataTableConverter
    {
        private static readonly IContractResolver _contractResolver = new DefaultContractResolver();
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            var contractResolver = serializer.ContractResolver;
    
            try
            {
                serializer.ContractResolver = _contractResolver;
                base.WriteJson(writer, value, serializer);
            }
            finally
            {
                serializer.ContractResolver = contractResolver;
            }
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            var contractResolver = serializer.ContractResolver;
            object value = null;
    
            try
            {
                serializer.ContractResolver = _contractResolver;
                value = base.ReadJson(reader, objectType, existingValue, serializer);
            }
            finally
            {
                serializer.ContractResolver = contractResolver;
            }
    
            return value;
        }
    }
    

    You add this CustomDataTableConverter to the converters of your JsonSerializerSettings.

    var settings = new JsonSerializerSettings
    {
        ContractResolver = new DefaultContractResolver()
        {
            NamingStrategy = new CamelCaseNamingStrategy()
        },
        Formatting = Newtonsoft.Json.Formatting.Indented        
    };
    settings.Converters.Add(new CustomDataTableConverter());
    
    var datatable = new DataTable();
    datatable.Columns.Add("Period", typeof(string));
    datatable.Columns.Add("Sales", typeof(int));
    datatable.Columns.Add("Expenses", typeof(int));
    datatable.Rows.Add("2004",400, 700);
    datatable.Rows.Add("2005", 500, 900);
    datatable.Rows.Add("2006", 720, 420);
    
    var dto = new MySampleDto
    {
        Data = datatable,
        SomeInteger = 3
    };
    
    var json = JsonConvert.SerializeObject(dto, settings);
    

    The columns of the DataTable in the JSON result will now be Period, Sales and Expenses as expected.

    {
      "data": [
        {
          "Period": "2004",
          "Sales": 400,
          "Expenses": 700
        },
        {
          "Period": "2005",
          "Sales": 500,
          "Expenses": 900
        },
        {
          "Period": "2006",
          "Sales": 720,
          "Expenses": 420
        }
      ],
      "someInteger": 3
    }