Unmarshal map[string]DynamoDBAttributeValue в структуру
Я пытаюсь настроить AWS-лямбду, используя aws-sdk-go, который запускается всякий раз, когда новый user
добавляется к определенной таблице DynamodB.
Все работает просто отлично, но я не могу найти способ разархивировать карту map[string]DynamoDBAttributeValue
лайк:
{
"name": {
"S" : "John"
},
"residence_address": {
"M": {
"address": {
"S": "some place"
}
}
}
}
Например, для данной структуры User
структура. Здесь показан пример отмены map[string]*dynamodb.AttributeValue
в данный интерфейс, но я не могу найти способ сделать то же самое с map[string]DynamoDBAttributeValue
хотя эти типы, кажется, подходят для тех же целей.
map[string]DynamoDBAttributeValue
возвращается events.DynamoDBEvents
из пакета github.com/aws/aws-lambda-go/events
, Это мой код:
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)
}
}
}
Конечно, я могу встать record.Change.NewImage
в JSON и разархивировать его обратно в заданную структуру, но тогда мне придется вручную инициализировать dynamoUser
атрибуты, начиная с последних.
Или я мог бы даже написать функцию, которая анализирует map[string]DynamoDBAttributeValue
в map[string]*dynamodb.AttributeValue
лайк:
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
}
А потом просто использовать dynamodbattribute.UnmarshalMap
, но на events.DataTypeMap
это был бы довольно сложный процесс.
Есть ли способ, с помощью которого я могу разархивировать запись DynamoDB из events.DynamoDBEvent
в структуру с подобным методом, показанным для map[string]*dynamodb.AttributeValue
?
5 ответов
Я попробовал функцию, которую вы предоставили, и я столкнулся с некоторыми проблемами с events.DataTypeList
Таким образом, мне удалось написать следующую функцию, которая делает свое дело:
// 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)
}
Я был разочарован тем, что тип NewImage из записи не был map[string]*dynamicodb.AttributeValue, поэтому я мог использовать пакет dynamicodbattribute.
Представление JSON событий.DynamoDBAttributeValue, по-видимому, совпадает с представлением JSON для dynamicodb.AttributeValue.
Поэтому я попытался создать свой собственный тип DynamoDBEvent и изменил тип OldImage и NewImage, чтобы он отображался в map[string]*dynamicodb.AttributeValue вместо map[string]events.DynamoDBAttributeValue
Это немного некрасиво, но это работает для меня.
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"`
}
Я нашел ту же проблему, и решение состоит в том, чтобы выполнить простое преобразование типов. Это возможно, потому что в конце концов тип, полученный лямбда-событиямиevents.DynamoDBAttributeValue
и тип, используемый SDK версии 2 AWS DynamoDB.types.AttributeValue
одинаковы. Далее я покажу вам код преобразования.
package aws_lambda
import (
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue"
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
)
func UnmarshalDynamoEventsMap(
record map[string]events.DynamoDBAttributeValue, out interface{}) error {
asTypesMap := DynamoDbEventsMapToTypesMap(record)
err := attributevalue.UnmarshalMap(asTypesMap, out)
if err != nil {
return err
}
return nil
}
func DynamoDbEventsMapToTypesMap(
record map[string]events.DynamoDBAttributeValue) map[string]types.AttributeValue {
resultMap := make(map[string]types.AttributeValue)
for key, rec := range record {
resultMap[key] = DynamoDbEventsToTypes(rec)
}
return resultMap
}
// DynamoDbEventsToTypes relates the dynamo event received by AWS Lambda with the data type that is
// used in the Amazon SDK V2 to deal with DynamoDB data.
// This function is necessary because Amazon does not provide any kind of solution to make this
// relationship between types of data.
func DynamoDbEventsToTypes(record events.DynamoDBAttributeValue) types.AttributeValue {
var val types.AttributeValue
switch record.DataType() {
case events.DataTypeBinary:
val = &types.AttributeValueMemberB{
Value: record.Binary(),
}
case events.DataTypeBinarySet:
val = &types.AttributeValueMemberBS{
Value: record.BinarySet(),
}
case events.DataTypeBoolean:
val = &types.AttributeValueMemberBOOL{
Value: record.Boolean(),
}
case events.DataTypeList:
var items []types.AttributeValue
for _, value := range record.List() {
items = append(items, DynamoDbEventsToTypes(value))
}
val = &types.AttributeValueMemberL{
Value: items,
}
case events.DataTypeMap:
items := make(map[string]types.AttributeValue)
for k, v := range record.Map() {
items[k] = DynamoDbEventsToTypes(v)
}
val = &types.AttributeValueMemberM{
Value: items,
}
case events.DataTypeNull:
val = nil
case events.DataTypeNumber:
val = &types.AttributeValueMemberN{
Value: record.Number(),
}
case events.DataTypeNumberSet:
val = &types.AttributeValueMemberNS{
Value: record.NumberSet(),
}
case events.DataTypeString:
val = &types.AttributeValueMemberS{
Value: record.String(),
}
case events.DataTypeStringSet:
val = &types.AttributeValueMemberSS{
Value: record.StringSet(),
}
}
return val
}
Существует пакет, который позволяет конвертировать события.DynamoDBAttributeValue в dynamodb.AttributeValue.
https://pkg.go.dev/github.com/aereal/go-dynamodb-attribute-conversions/v2
Оттуда можно преобразовать AttributeValue в структуру
func Unmarshal(attribute map[string]events.DynamoDBAttributeValue, out interface{}) error {
av := ddbconversions.AttributeValueMapFrom(attribute)
return attributevalue.UnmarshalMap(av, out)
}
На данный момент мне удалось решить эту проблему путем реализации функции, о которой я говорил в этом вопросе.
// 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
}
Но я все еще открыт для других реализаций.