web-dev-qa-db-fra.com

Unmarshal map [string] DynamoDBAttributeValue dans une structure

J'essaie de configurer un AWS-lambda en utilisant aws-sdk-go qui est déclenché chaque fois qu'une nouvelle user est ajoutée à une certaine table dynamodb .

Tout fonctionne très bien, mais je ne trouve pas le moyen de décompresser une carte map[string]DynamoDBAttributeValue comme:

{
    "name": {
        "S" : "John"
    },
    "residence_address": {
        "M": {
            "address": {
                "S": "some place"
            }
        }
    }
}

Pour une structure donnée, par exemple, une structure User. _ { Here } montre un exemple de décompression d'un map[string]*dynamodb.AttributeValue dans une interface donnée, mais je ne trouve pas le moyen de faire la même chose avec map[string]DynamoDBAttributeValue même si ces types semblent correspondre aux mêmes objectifs.

map[string]DynamoDBAttributeValue est retourné par un events.DynamoDBEvents du paquet github.com/aws/aws-lambda-go/events. Ceci est mon code:

package handler

import (
    "context"
    "github.com/aws/aws-lambda-go/events"
    "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
    "github.com/aws/aws-sdk-go/service/dynamodb"
)

func HandleDynamoDBRequest(ctx context.Context, e events.DynamoDBEvent) {

    for _, record := range e.Records {

        if record.EventName == "INSERT" {

            // User Struct
            var dynamoUser model.DynamoDBUser

            // Of course this can't be done for incompatible types
            _ := dynamodbattribute.UnmarshalMap(record.Change.NewImage, &dynamoUser)
        }

    }

}

Bien sûr, je peux marshaler record.Change.NewImage en JSON et le remettre dans une structure donnée, mais je devrais alors initialiser manuellement les attributs dynamoUser à partir de ceux-ci.

Ou je pourrais même écrire une fonction qui analyse map[string]DynamoDBAttributeValue en map[string]*dynamodb.AttributeValue comme:

func getAttributeValueMapFromDynamoDBStreamRecord(e events.DynamoDBStreamRecord) map[string]*dynamodb.AttributeValue {
    image := e.NewImage
    m := make(map[string]*dynamodb.AttributeValue)
    for k, v := range image {
        if v.DataType() == events.DataTypeString {
            s := v.String()
            m[k] = &dynamodb.AttributeValue{
                S : &s,
            }
        }
        if v.DataType() == events.DataTypeBoolean {
            b := v.Boolean()
            m[k] = &dynamodb.AttributeValue{
                BOOL : &b,
            }
        }
        // . . .
        if v.DataType() == events.DataTypeMap {
            // ?
        }
    }
    return m
}

Et puis, utilisez simplement dynamodbattribute.UnmarshalMap, mais sur events.DataTypeMap, ce serait un processus assez délicat.

Existe-t-il un moyen de disséminer un enregistrement DynamoDB provenant d'un events.DynamoDBEvent dans une structure avec une méthode similaire à celle affichée pour map[string]*dynamodb.AttributeValue?

10
AndreaM16

J'ai essayé la fonction que vous avez fournie et j'ai rencontré quelques problèmes avec events.DataTypeList. J'ai donc réussi à écrire la fonction suivante qui fait l'affaire:

// UnmarshalStreamImage converts events.DynamoDBAttributeValue to struct
func UnmarshalStreamImage(attribute map[string]events.DynamoDBAttributeValue, out interface{}) error {

    dbAttrMap := make(map[string]*dynamodb.AttributeValue)

    for k, v := range attribute {

        var dbAttr dynamodb.AttributeValue

        bytes, marshalErr := v.MarshalJSON(); if marshalErr != nil {
            return marshalErr
        }

        json.Unmarshal(bytes, &dbAttr)
        dbAttrMap[k] = &dbAttr
    }

    return dynamodbattribute.UnmarshalMap(dbAttrMap, out)

}
4
Sky.Li

J'étais frustré que le type de NewImage de l'enregistrement ne soit pas map [string] * dynamodb.AttributeValue afin que je puisse utiliser le package dynamodbattribute.

La représentation JSON de events.DynamoDBAttributeValue semble être identique à la représentation JSON de dynamodb.AttributeValue.

J'ai donc essayé de créer mon propre type DynamoDBEvent et de modifier le type OldImage et NewImage afin qu'il soit structuré dans map [string] * dynamodb.AttributeValue au lieu de map [string] events.DynamoDBAttributeValue

C'est un peu moche mais ça marche pour moi.

package main

import (
    "github.com/aws/aws-lambda-go/events"
    "github.com/aws/aws-lambda-go/lambda"
    "github.com/aws/aws-sdk-go/service/dynamodb"
    "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
    "fmt"
)

func main() {

    lambda.Start(lambdaHandler)
}

// changed type of event from: events.DynamoDBEvent to DynamoDBEvent (see below)
func lambdaHandler(event DynamoDBEvent) error {

    for _, record := range event.Records {

        change := record.Change
        newImage := change.NewImage // now of type: map[string]*dynamodb.AttributeValue

        var item IdOnly
        err := dynamodbattribute.UnmarshalMap(newImage, &item)
        if err != nil {
            return err
        }

        fmt.Println(item.Id)
    }

    return nil
}

type IdOnly struct {
    Id string `json:"id"`
}

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"`
    // changed to map[string]*dynamodb.AttributeValue
    Keys                        map[string]*dynamodb.AttributeValue `json:"Keys,omitempty"`
    // changed to map[string]*dynamodb.AttributeValue
    NewImage                    map[string]*dynamodb.AttributeValue `json:"NewImage,omitempty"`
    // changed to map[string]*dynamodb.AttributeValue
    OldImage                    map[string]*dynamodb.AttributeValue `json:"OldImage,omitempty"`
    SequenceNumber              string                              `json:"SequenceNumber"`
    SizeBytes                   int64                               `json:"SizeBytes"`
    StreamViewType              string                              `json:"StreamViewType"`
}
0
noIdeaAtAll

Pour le moment, j'ai réussi à résoudre ce problème en mettant en œuvre la fonction que j'ai mentionnée dans la question.

// May take either `map[string]DynamoDBAttributeValue` 
// or `DynamoDBAttributeValue` as input.
func EventStreamToMap(attribute interface{}) map[string]*dynamodb.AttributeValue {

    // Map to be returned
    m := make(map[string]*dynamodb.AttributeValue)

    tmp := make(map[string]events.DynamoDBAttributeValue)

    switch t := attribute.(type) {
    case map[string]events.DynamoDBAttributeValue:
        tmp = t
    case events.DynamoDBAttributeValue:
        tmp = t.Map()
    }

    for k, v := range tmp {
        switch v.DataType() {
        case events.DataTypeString:
            s := v.String()
            m[k] = &dynamodb.AttributeValue{
                S : &s,
            }
        case events.DataTypeBoolean:
            b := v.Boolean()
            m[k] = &dynamodb.AttributeValue{
                BOOL : &b,
            }
        // case events.SomeOtherType:
        //     ...
        case events.DataTypeMap:
            m[k] = &dynamodb.AttributeValue{
                M : EventStreamToMap(v),
            }
        }
    }
    return m
}

Mais je suis toujours ouvert à d'autres implémentations.

0
AndreaM16