Search code examples
c#aws-lambdaamazon-dynamodbamazon-dynamodb-streams

How to convert Dynamodb stream DynamoDBEvent new image or old image to my own concrete object type T without manual mapping?


I have a lambda function that is triggered from a DynamoDB stream event. The object it accepts is a DynamoDBEvent from the Amazon.Lambda.DynamoDBEvents Nuget package.

public async Task FunctionHandler(DynamoDBEvent event, ILambdaContext context)
{
    foreach (var record in event.Records)
    {
        var newImage = event.Dynamodb.NewImage; // Or "OldImage" property
        var myObject = new MyObject();

        myObject.ItemId = newImage["ItemId"].N;
        myObject.IdList = newImage["IdList"].NS;
        myObject.CustomerName = newImage["CustomerName"].S;
    }
}

Is it possible to easily convert this event to my own class, like the one below, eg:

public class MyObject 
{
    public int ItemId { get; set; }
    public List<int> IdList { get; set; }
    public string CustomerName { get; set; }
}

Without having to create my own mapper that manipulates the DynamoDBEvent object for each individual property of every type?

The AttributeValue class contains lots of different properties, one for each type the DynamoDb supports and my object may become complex with nested properties in the future.

The Document model from the DynamodDB library seems to be able to do it already...

NewImage and OldImage is of type Dictionary<string, AttributeValue>.


Solution

  • The Amazon.Lambda.DynamoDBEvents Nuget package that contains the definitions for DynamoDBEvent already has a dependency on the package AWSSDK.DynamoDBv2 which already contains the code for the .NET DynamoDB Document Model to convert from a Dictionary<string, AttributeValue> to another object.

    We can write a helper method that utilises FromAttributeMap and FromDocument to convert it:

    using Amazon;
    using Amazon.DynamoDBv2;
    using Amazon.DynamoDBv2.DataModel;
    using Amazon.DynamoDBv2.DocumentModel;
    using Amazon.DynamoDBv2.Model;
    
    public class DynamoDbStreamToObject
    {
        private static readonly AmazonDynamoDBConfig ClientConfig = new AmazonDynamoDBConfig { RegionEndpoint = RegionEndpoint.EUWest1 };
        private static readonly DynamoDBContext DynamoDbContext = new DynamoDbContext(newAmazonDynamoDBClient(ClientConfig));
    
        public static T Convert<T>(Dictionary<string, AttributeValue> dynamoDbImage) 
        {
            var dynamoDocument = Document.FromAttributeMap(dynamoDbImage);
            return DynamoDbContext.FromDocument<T>(dynamoDocument);
        }
    }
    

    First it converts Dictionary<string, AttributeValue> to an Amazon DynamoDB Document and from that to your object.

    Your function handler can now be simplified by using this helper method to the following:

    public async Task FunctionHandler(DynamoDBEvent event, ILambdaContext context)
    {
        foreach (var record in input.Records)
        {
            var newImage = event.Dynamodb.NewImage; // Or "OldImage" property
            var myObject = DynamoDbStreamToObject.Convert<MyObject>(newImage);
        }
    }
    

    Unfortunately this requires a kind've redundant DynamoDBContext (Redundant because it's not connecting to DynamoDB itself), but it seems to work.