How to Handle Numbers Represented as Strings in the Input to a .NET AWS Lambda Function
Download full source code.
If you are using the lambda.EmptyFunction
template, deserialization will be performed by Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer
.
This DefaultLambdaJsonSerializer
cannot handle numbers represented as strings.
For example, if you have a Car
type which is the input to the function handler, it might look like this -
public class Function
{
public string FunctionHandler(Car input, ILambdaContext context)
{
return input.ToString();
}
}
public class Car
{
public string Make { get; set; }
public string Model { get; set; }
public int Year { get; set; }
public override string ToString()
{
return $"Your car is a {Make} {Model}, made in {Year}.";
}
}
If you invoke it with the following payload it will work -
dotnet lambda invoke-function HandlingNumbersAsStringAndEnums --payload '{"make":"Ford","model":"Focus","year":2022}'
Payload:
"Your car is a Ford Focus, made in 2022."
But if you invoke it with this payload it will fail -
dotnet lambda invoke-function HandlingNumbersAsStringAndEnums --payload '{"make":"Ford","model":"Focus","year":"2022"}'
Payload:
{
"errorType": "JsonSerializerException",
"errorMessage": "Error converting the Lambda event JSON payload to type HandlingNumbersAsStringsInJson.Car: The JSON value could not be converted to System.Int32. Path: $.year | LineNumber: 0 | BytePositionInLine: 44.",
The difference is that the year
property is a string in the second payload. The DefaultLambdaJsonSerializer
cannot handle this.
You have two choices to fix this -
- Create your own implementation of a serializer/deserializer and use that in your Lambda function.
- Instead of taking a
Car
as the input to your function handler, take aStream
and deserialize it yourself.
Creating your own implementation of a serializer/deserializer
This is not a difficult task. Use the DefaultLambdaJsonSerializer
as a base class and set your serialization options there.
using System.Text.Json;
using System.Text.Json.Serialization;
using Amazon.Lambda.Serialization.SystemTextJson;
namespace HandlingNumbersAsStringsInJson;
public class NumbersAsStringsSerializer : DefaultLambdaJsonSerializer
{
public NumbersAsStringsSerializer()
: base(ConfigureJsonSerializerOptions) { }
private static void ConfigureJsonSerializerOptions(JsonSerializerOptions options)
{
options.NumberHandling = JsonNumberHandling.AllowReadingFromString;
}
// This is a shorter version of the above
// public NumbersAsStringsSerializer()
// : base((options) => options.NumberHandling = JsonNumberHandling.AllowReadingFromString) { }
}
Then in the Function.cs
file, change the serializer to the one you just created -
[assembly: LambdaSerializer(typeof(NumbersAsStringsSerializer))]
Now you can invoke the function with the payload that has a string for the year
property and it will work.
Taking a Stream as the input to your Lambda function
The second option is to take a stream as the input to your function handler instead of the Car
type and perform the deserialization there.
public string FunctionHandler(Stream input, ILambdaContext context)
{
var options = new JsonSerializerOptions
{
NumberHandling = JsonNumberHandling.AllowReadingFromString,
PropertyNameCaseInsensitive = true
};
var car = JsonSerializer.Deserialize<Car>(input, options);
return car.ToString();
}
For better efficiency you can move var options =...
outside the function handler. That way, the code is run only the first time the function is invoked.
In the attached zip file you will see that the DefaultLambdaJsonSerializer
is still in place, it will be used to serialize the response back to the caller.
In a follow up I’ll show how to handle enums in the same way.
Download full source code.