As part of migrating some existing .NET Core 3.0 worker services running on EC2 to AWS Lambda, we've coded-up a custom ILambdaSerializer
implementation to handle deserializing payloads to the Lambda function, such as SQSEvent
.
While this is relatively simple to do, it would be nice if a serializer implementation for System.Text.Json was built into the Lambda SDK to save the need for copy-paste across different function codebases.
One shortcoming with the state-of-things as of today is that PropertyNameCaseInsensitive
has to be set to true
on the serializer options as the events don't use consistent PascalCase vs. camelCase naming, which incurs a performance penalty (I don't have a quantifier for how much the penalty is). In the case of SQSEvent
I believe it's eventSourceARN
that trips it up as it doesn't map over to EventSourceArn
.
I imagine this could be easily fixed in the event libraries by adding [JsonPropertyName("...")]
attribute annotations to the properties on the models.
I believe further benefit could be gained if there was asynchronous methods on the interface to leverage the async support of System.Text.Json.JsonSerializer
and consume the Stream
directly, but that's a bigger change that just providing an implementation based on the current interface.
Below is the code we're using. It might benefit from further optimization, but as it is right now it's working well for us:
using System.IO;
using System.Text.Json;
using Amazon.Lambda.Core;
namespace MyLambdaFunction
{
public sealed class SystemTextJsonLambdaSerializer : ILambdaSerializer
{
private readonly JsonSerializerOptions _options;
public SystemTextJsonLambdaSerializer()
{
// SQSEvent does not use a consistent camel/Pascal case naming convention,
// so as we cannot annotate with attributes, the options need to be case
// insensitive to be tolerant for things to work properly when deserializing.
_options = new JsonSerializerOptions()
{
IgnoreNullValues = true,
PropertyNameCaseInsensitive = true,
};
}
public T Deserialize<T>(Stream requestStream)
{
using var copy = new MemoryStream();
requestStream.CopyTo(copy);
byte[] utf8Json = copy.ToArray();
return JsonSerializer.Deserialize<T>(utf8Json, _options);
}
public void Serialize<T>(T response, Stream responseStream)
{
using var writer = new Utf8JsonWriter(responseStream);
JsonSerializer.Serialize(writer, response, _options);
}
}
}
/cc @normj
Thanks @martincostello. I'm gonna move this to our Lambda .NET repo.
Just a note, @martincostello that wont work for API Gateway.
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
You need to add this too or the response will be considered malformed due to pascal casing on the properties.
Thanks @phillip-haydon that is helpful.
I have been working on official support for this in the system.text.json branch.
I'm working my way through the existing event tests to make sure the new serializer works the same as the Newtonsoft one. Its still very much a work in progress but feel free to take a look and provide feedback.
The PR for this has been created with a link to some preview builds of the NuGet packages for those that are currently trying out .NET Core 3.1 with the Lambda custom runtime feature. https://github.com/aws/aws-lambda-dotnet/pull/568.
I'm going to close this issue and use the PR to track this feature going forward.
Most helpful comment
Thanks @martincostello. I'm gonna move this to our Lambda .NET repo.