'AWS dynamo stream processing using Go lambda

I'm using an AWS Lambda function written in Go with Dynamo stream but I don't see any way where I can marshall the old & new image to my struct because it's returning the image as map[string]DynamoDBAttributeValue.
I can check individual key and then assign it to my struct one by one, but is there an direct way to Marshall directly?

func HandleRequest(ctx context.Context, event events.DynamoDBEvent) {
    for _, record := range event.Records {

        var newStruct models.MyStruct // want to marshall newImage in this struct

        logger.Debugf("New: %#v", record.Change.NewImage)
    }
}

UPDATE: Here is the custom DynamoDBEvent that I'm using now:

type DynamoDBEvent struct {
    Records []DynamoDBEventRecord `json:"Records"`
}

type DynamoDBEventRecord struct {
    AWSRegion      string                       `json:"awsRegion"`
    Change         DynamoDBStreamRecord         `json:"dynamodb"`
    EventID        string                       `json:"eventID"`
    EventName      string                       `json:"eventName"`
    EventSource    string                       `json:"eventSource"`
    EventVersion   string                       `json:"eventVersion"`
    EventSourceArn string                       `json:"eventSourceARN"`
    UserIdentity   *events.DynamoDBUserIdentity `json:"userIdentity,omitempty"`
}

type DynamoDBStreamRecord struct {
    ApproximateCreationDateTime events.SecondsEpochTime             `json:"ApproximateCreationDateTime,omitempty"`
    Keys                        map[string]*dynamodb.AttributeValue `json:"Keys,omitempty"`
    NewImage                    map[string]*dynamodb.AttributeValue `json:"NewImage,omitempty"`
    OldImage                    map[string]*dynamodb.AttributeValue `json:"OldImage,omitempty"`
    SequenceNumber              string                              `json:"SequenceNumber"`
    SizeBytes                   int64                               `json:"SizeBytes"`
    StreamViewType              string                              `json:"StreamViewType"`
}


Solution 1:[1]

Unfortunately there is no elegant way yet, sot you have to check each key/value pairs and assign them to your struct.

PS: As small sugar you can use func FromDynamoDBMap from this package and work with map[string]interface{} not with map[string]events.DynamoDBAttributeValue which is way easier.

Solution 2:[2]

I have created a solution for the problem. This was crucial to my implementation since my implementation uses custom keys.

Unfortunately, the Number type has to be cast to Float64 to make sure that it is not lost.

This will result in a completely usable struct against the changeset. You may need a map of your models and a known field

Usage

newResult, _ := UnmarshalStreamImage(record.Change.NewImage)
newMarshal, _ := attributevalue.MarshalMap(newResult)
model := SomeModel{}
err := attributevalue.UnmarshalMap(newMarshal, &model)

Function Code




// UnmarshalStreamImage converts events.DynamoDBAttributeValue to struct
func UnmarshalStreamImage(attribute map[string]events.DynamoDBAttributeValue) (map[string]interface{}, error) {
    baseAttrMap := make(map[string]interface{})
    for k, v := range attribute {
        baseAttrMap[k] = extractVal(v)
    }
    return baseAttrMap, nil
}

func extractVal(v events.DynamoDBAttributeValue) interface{} {
    var val interface{}
    switch v.DataType() {
    case events.DataTypeString:
        val = v.String()
    case events.DataTypeNumber:
        val, _ = v.Float()
    case events.DataTypeBinary:
        val = v.Binary()
    case events.DataTypeBoolean:
        val = v.Boolean()
    case events.DataTypeNull:
        val = nil
    case events.DataTypeList:
        list := make([]interface{}, len(v.List()))
        for _, item := range v.List() {
            list = append(list, extractVal(item))
        }
        val = list
    case events.DataTypeMap:
        mapAttr := make(map[string]interface{}, len(v.Map()))
        for k, v := range v.Map() {
            mapAttr[k] = extractVal(v)
        }
        val = mapAttr
    case events.DataTypeBinarySet:
        set := make([][]byte, len(v.BinarySet()))
        for _, item := range v.BinarySet() {
            set = append(set, item)
        }
        val = set
    case events.DataTypeNumberSet:
        set := make([]string, len(v.NumberSet()))
        for _, item := range v.NumberSet() {
            set = append(set, item)
        }
        val = set
    case events.DataTypeStringSet:
        set := make([]string, len(v.StringSet()))
        for _, item := range v.StringSet() {
            set = append(set, item)
        }
        val = set
    }
    return val
}```

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 cn007b
Solution 2 Gnanakeethan Balasubramaniam