Serializing a PascalCase Newtonsoft.Json JObject to camelCase 您所在的位置:网站首页 pascalcasing Serializing a PascalCase Newtonsoft.Json JObject to camelCase

Serializing a PascalCase Newtonsoft.Json JObject to camelCase

2023-09-21 00:33| 来源: 网络整理| 查看: 265

In this post I describe one of the quirks of serializing JSON.NET JObject (contract resolvers are not honoured), and show how to get camelCase names when serializing a JObject that stored its property names in PascalCase.

Background - using JObject for dynamic data

I was working with some code the other day that stored objects in PostgreSQL using the built-in JSON support. The code that used it was deserializing the data to a JSON.NET JObject in code. So the data class looked something like the following, with a JObject property, Details:

public class Animal { public Guid Id { get; set; } public string Name { get; set; } public string Genus { get; set; } public JObject Details { get; set; } }

In this case, the JObject was a "serialized" version of a data class:

public class SlothDetails { public int NumberOfToes { get; set; } public int NumberOfCervicalVertebrae { get; set; } }

So an instance of the Animal class was created using code similar to the following:

var sloth = new Animal { Id = Guid.NewGuid(), Name = "Three-toed sloth", Genus = "Bradypus", Details = JObject.FromObject(new SlothDetails { NumberOfToes = 3, NumberOfCervicalVertebrae = 9, }) };

In this code we take the strongly-typed SlothDetails and turn it into a JObject. There's nothing very special here - we're using JObject as a pseudo-dynamic type, to allow us to store different types of data in the Details property. For example, we could create an entirely different type and assign it to the Details property:

public class DogDetails { public bool IsGoodBoy { get; set; } public int NumberOfLegs { get; set; } } var dog = new Animal { Id = Guid.NewGuid(), Name = "Dog", Genus = "Canis", Details = JObject.FromObject(new DogDetails { IsGoodBoy = true, NumberOfLegs = 4, }) };

We can work with both Animal instances in the same way, even though the Details property contains different data.

So why would you want to do this? The type system is one of the strong points of C#, and if you need truly dynamic data, there's always the dynamic type from C# 4.0? Why not create Animal and make Details a T? Or we could have just made Details a string, and stored a serialized version of the data?

All of those might be valid approaches for your situation. In our case, we know that we're storing JSON in the database, and that the Details object must serialize to JSON, so it made sense to use a type that most accurately represents that data: JObject. LINQ to JSON provides a convenient API to query the data, and we get some type safety from knowing that anything passed to Details is a valid JSON object.

All of this works perfectly, until you try exposing one of these JObject from an ASP.NET Web API.

JSON.NET, serialization, and camelCase

Lets start by seeing what you get if you Serialize the dog instance above, by returning it from an ASP.NET Core controller:

[ApiController, Route("api/[controller]")] public class AnimalsController { [HttpGet] public object Get() { return new Animal { Id = Guid.NewGuid(), Name = "Dog", Genus = "Canis", Details = JObject.FromObject(new DogDetails { IsGoodBoy = true, NumberOfLegs = 4, }) }; } }

ASP.NET Core uses a camelCase formatter by default (instead of the PascalCase used for C# property names) so the resulting JSON is camelCase:

{ "id": "96ca7c68-7550-4809-86c5-4d784f3b3f87", "name": "Dog", "genus": "Canis", "details": { "IsGoodBoy": true, "NumberOfLegs": 4 } }

This looks nearly right, but there's a problem - the IsGoodBoy and NumberOfLegs properties of the serialized JObject are all PascalCase - that's a problem!

This all comes down to an early design-decision (since lamented) that means that contract resolvers are ignored by default when serializing JObject as part of an object graph (as we have here).

This is clearly an issue if you're using a JObject in your object graph. I only found a few ways to work around the limitation, depending on your situation.

1. Change the global serialization settings

The first option is to change the global serialization options to use camelCase. This option has been available for a long time (since version 5.0 of JSON.NET), and will globally configure the JSON.NET serializer to use camelCase, even for a JObject created using PasalCase property names:

// Add this somewhere in your project, most likely in Startup.cs JsonConvert.DefaultSettings = () => new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() };

If you add this code to your project, your JObject will be serialized to camelCase, with no other changes required to your project:

{ "id": "96ca7c68-7550-4809-86c5-4d784f3b3f87", "name": "Dog", "genus": "Canis", "details": { "isGoodBoy": true, "numberOfLegs": 4 } }

The obvious downside to this approach is that affects all serialization in your app. If you have a new (or small app) that might not be a problem, but for a large, legacy app that might cause issues. Subtle changes in casing in client-side JavaScript apps could cause a multitude of bugs if you have code relying on the existing JObject serialization behaviour.

2. Don't create PascalCase JObjects

The serialization problem we're seeing stems from two things:

JObject serialization doesn't honour contract resolvers (unless you set JsonConvert.DefaultSettings) We have a PascalCase JObject instead of camelCase.

The first approach (changing the default serializer) addresses point 1., but the other option is to never get ourselves into this situation! This approach is easier to introduce to large apps, as it allows you to change the "stored format" in a single location, instead of affecting a whole large app.

The JObject.FromObject() method takes a second parameter, which allows you to control how the JObject is created from the C# object. We can use that to ensure the JObject we create uses camelCase names already, so we don't have to worry when it comes to serialization.

To do this, create a JsonSerializer with the required settings. You can store this globally and reuse it throughout your app:

static JsonSerializer _camelCaseSerializer = JsonSerializer.Create( new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() });

Now, when creating the JObject from a .NET object, pass in the _camelCaseSerializer:

var details = JObject.FromObject(new DogDetails { IsGoodBoy = true, NumberOfLegs = 4, }, _camelCaseSerializer) //


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有