Improved AWS Lambda JSON Serialization in C#

AWS lambda functions are the foundation of serverless computing on AWS. They are useful to react to AWS events (SNS message published, file uploaded on S3, etc) but are also often used to just do some serverless computing. You invoke your lambda with some input, it computes something and returns the result. To be more precise, you invoke your lambda function with a JSON object and it will return another JSON object.
This article will first show you some of the limitations (or surprising behaviors) of the default .net AWS Lambda JSON serializer. It will then demonstrate how you can extend this serializer to have an improved JSON serializer.
Surprises with the default JSON serializer
As explained here in the “Serializing Lambda Functions” section, you can use the Amazon.Lambda.Serialization.Json
nuget package to deserialize/serialize your lambda function inputs/outputs. This is based on the very popular JSON.NET library that you probably already use.
Let’s write a very simple lambda function that takes an object and returns an object:
As you can see, the function simply returns the input by modifying the message
property and adding a responseTime
property.
Let’s invoke the function with the following input:
{
"message": "Hello Lambda",
"volume": "High",
"requestTime": "2019-09-28T11:27:09.4268686Z"
}
And here’s the response (that was reformatted manually in multi-line format for easier reading):
{
"Message": "HELLO LAMBDA",
"Volume": 2,
"RequestTime": "2019-09-28T11:27:09.4268686Z",
"ResponseTime": "2019-09-28T08:43:15.8986554-04:00"
}
As you can see, there are a few surprises:
- Enumerated types are serialized as integers: The
volume
wasHigh
in the request but it was2
in the response. This is not very useful from the point of view of the caller. This is not the fault of the AWS serializer. It’s the default behavior of the JSON.NET serializer. - Response uses PascalCase: if you’re like me (and lots of programmers), you follow the Google guideline that says to format JSON documents using camelCase but you also follow the C# guideline that says to format class properties using PascalCase. Unfortunately, the default JSON.NET serializer preserves the casing of the C# properties in the resulting JSON document. So, if you follow the C# guideline, you will break the Google guideline with JSON.NET (by default). This is not a big deal for most people. But if you use theses JSON objects into an AWS step function, it can become a big deal. This is because the Amazon States Language is case sensitive. You therefore want the lambdas invoked by a step function to preserve the casing of the objects they return.
- Timestamps in local format: a final, and less significant point, is that
DateTime
objects that are inLocal
format are not formatted in UTC (see theResponseTime
member). If you’re part of a team that has standardized on returning date-times in UTC format, this can be a problem.
As you can see, the default behavior of Amazon’s JsonSerializer
is not ideal. In fact, it’s not really Amazon’s fault. It’s just the default behavior of JSON.NET.
How to fix it?
Luckily for us, there are extensibility options that can solve these issues. I did a first PR to add the possibility to set the serializer options and a second one to control the naming strategy. With these two improvements in place, it is possible to define your own JSON serializer with the desired options:
Note that you will need version 1.7.0 or higher of the Amazon.Lambda.Serialization.Json
nuget package for this code to compile.
You can now use this serializer as the default serializer. To do so, replace the line:
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))]
By this one:
[assembly: LambdaSerializer(typeof(WellBehavedJsonSerializer))]
Once this is done, invoking the test function will return this:
{
"message": "HELLO LAMBDA",
"volume": "High",
"requestTime": "2019-09-28T11:27:09.4268686Z",
"responseTime": "2019-10-25T09:06:58.3326187Z"
}
So we now have a JSON object that:
- uses camel case
- serializes enums as strings
- serializes
DateTime
in UTC
Conclusion
As you’ve seen, it’s easy to customize the behavior of the JSON serializer by:
- creating a class that derives from
Amazon.Lambda.Serialization.Json.JsonSerializer
- customizing the serializer settings and naming strategy in the constructor
- changing the class used in the
LambdaSerializerAttribute
With these tools in place, you are now able to go ahead and use the JSON formatting guidelines that your team has agreed on.