My son would say JSON and Serialization go together like peas and ketchup. I however only find that combination acceptable when it’s meatloaf night. The rest of us who deal with JSON know that it’s a great tool for passing some things around, but you don’t keep it as a string once you are dealing with it in your applications. To that end, we have created DynamicJsonPropertyNamingPolicy.
Enough meatloaf, what’s the problem?
In our current environment at ACV, we have a wide array of technologies in use. Unfortunately, several of them like to serialize and deserialize JSON in different ways. Our Python services like snake_case
, our C# services like PascalCase
, our JavaScript front ends and others like camelCase
.
There are some ways to handle this today with things like JsonPropertyName
that allow you to handle this at a low level. However, this can be tiresome if you need to create 3-4 implementations for different ways to serialize the same data or you have different clients who want the data in different formats. You can take a Backend-for-frontend (BFF) approach but that might be overkill just for serialization formats.
Following inspiration from examples specifically like this for JSON.net there was already a few ways to handle this. However, we didn’t find an existing one for the new System.Text.Json
libraries introduced in .NET Core 3.0.
How we fixed the problem
Enter: DynamicJsonPropertyNamingPolicy (source here)
How to use it
For output
Now it would be nice if you could just use:
public void ConfigureServices(IServiceCollection services)
{
// THIS IS WRONG!
services.AddControllers()
.AddJsonOptions(options =>
options.JsonSerializerOptions.PropertyNamingPolicy
= new DynamicJsonPropertyNamingPolicy());
}
However, since that happens once at startup, it’s not possible to have it be dynamic and the JsonSerializerOptions
handles metadata and caching for the different types you serialize. This provides additional performance improvements, but limits our options for a situation like we have today.
So instead, it has to be handled at each controller method directly:
return new JsonResult(result, HttpContext.GetJsonSerializerOptions());
or from some kind of base class if you want to make things consistent across several controllers:
public abstract class SerializingBaseController : ControllerBase
{
protected JsonResult JsonResult(object result)
{
return new JsonResult(result, HttpContext.GetJsonSerializerOptions());
}
}
Then just note the lack of new
for the returns then:
public class WeatherController : SerializingControllerBase
{
...
[HttpGet]
public ActionResult<IEnumerable<WeatherForecast>> Get()
{
...
return JsonResult(result, HttpContext.GetJsonSerializerOptions());
}
}
For input
Luckily, handling input dynamically is much easier. This is thanks to the TextInputFormatter
class. Creating a derived type in the DynamicJsonPropertyNamingPolicy package allows for easy usage in the application setup:
builder.Services.AddControllers(o =>
{
o.InputFormatters.Insert(0, new DynamicSystemTextJsonInputFormatter());
});
Well that was fun
In addition to being a exercise in catching null
values when binding to data models when crossing systems, this also became our first published nuget package as well as one of the first open source projects here at ACV. Many more to come, so keep you eyes open on our repos (and our jobs!)