Skip to content

Instantly share code, notes, and snippets.

@justcallmelarry
Forked from andream16/main.go
Last active January 3, 2023 14:03
Show Gist options
  • Save justcallmelarry/0aaaf5eab0f0d529fd3c6c5e79c5bd22 to your computer and use it in GitHub Desktop.
Save justcallmelarry/0aaaf5eab0f0d529fd3c6c5e79c5bd22 to your computer and use it in GitHub Desktop.
Build ExpressionAttributeValues and UpdateExpression from map[string]interface{} - AWS Go SDK, DynamoDB
package main
import (
"fmt"
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/fatih/structs"
)
type TestStruct struct {
Name string
NestedStuff NestedStuff
SuperNestedStuff SuperNestedStuff
}
type NestedStuff struct {
Something string
}
type SuperNestedStuff struct {
SomethingNested NestedStuff
}
// Useful for https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/dynamo-example-update-table-item.html
//
// input := &dynamodb.UpdateItemInput{
// ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
// ":r": {
// N: aws.String("0.5"),
// },
// },
// TableName: aws.String("Movies"),
// Key: map[string]*dynamodb.AttributeValue{
// "year": {
// N: aws.String("2015"),
// },
// "title": {
// S: aws.String("The Big New Movie"),
// },
// },
// ReturnValues: aws.String("UPDATED_NEW"),
// UpdateExpression: aws.String("set info.rating = :r"),
// }
func main() {
// Just a test nested Struct
test := TestStruct{
Name: "hello",
NestedStuff: NestedStuff{
Something: "something1",
},
SuperNestedStuff: SuperNestedStuff{
SomethingNested: NestedStuff{
Something: "something2",
},
},
}
// Get map[string]interface{} from struct
m := structs.Map(test)
// Get ExpressionAttributeValues from a map[string]interface{}.
// Second parameter is parent's name that is used recursively. The first time it's an empty string.
exprAttrValues := BuildExpressionAttributeValues(m, "")
fmt.Println(fmt.Sprintf("ExpressionAttributeValues %v", exprAttrValues))
// ExpressionAttributeValues map[:name:{
// S: "hello"
// } :nestedstuffsomething:{
// S: "something1"
// } :supernestedstuffsomethingnestedsomething:{
// S: "something2"
// }]
// Get ExpressionAttributeNames from a map[string]interface{}.
exprAttrNames := BuildExpressionAttributeNames(m)
fmt.Println(fmt.Sprintf("ExpressionAttributeNames %v", exprAttrNames))
// Get UpdateExpression from a map[string]interface{}.
// Second and Third parameters are parent's name that are used recursively. The first time they are both empty strings.
updateExpr := BuildUpdateExpression(m, "", "")
// ExpressionAttributeNames map[#name:0x14000013c80 #nestedstuff:0x14000013c90 #something:0x14000013cd0 #somethingnested:0x14000013cc0 #supernestedstuff:0x14000013cb0]
fmt.Println(fmt.Sprintf("UpdateExpression %v", updateExpr))
// UpdateExpression #name = :name, #nestedstuff.#something = :nestedstuffsomething, #supernestedstuff.#somethingnested.#something = :supernestedstuffsomethingnestedsomething
}
func BuildExpressionAttributeValues(input map[string]interface{}, parentName string) map[string]*dynamodb.AttributeValue {
// Map to be returned
m := make(map[string]*dynamodb.AttributeValue)
for k, v := range input {
keyName := strings.Join([]string{":", parentName, strings.ToLower(k)}, "")
// add more type cases if more are added to the struct
switch t := v.(type) {
case map[string]interface{}:
nextName := strings.TrimPrefix(keyName, ":")
// create a similar returned map from the child node and add it to the current return map
child := BuildExpressionAttributeValues(t, nextName)
for k, v := range child {
m[k] = v
}
case int:
m[keyName] = &dynamodb.AttributeValue{
N: aws.String(fmt.Sprint(t)),
}
case string:
m[keyName] = &dynamodb.AttributeValue{
S: aws.String(t),
}
case bool:
m[keyName] = &dynamodb.AttributeValue{
BOOL: aws.Bool(t),
}
}
}
return m
}
func BuildExpressionAttributeNames(input map[string]interface{}) map[string]*string {
// Map to be returned
m := make(map[string]*string)
for k, v := range input {
keyName := strings.Join([]string{"#", strings.ToLower(k)}, "")
keyValue := string(k)
// add more type cases if more are added to the struct
switch t := v.(type) {
case string, bool, int:
m[keyName] = &keyValue
case map[string]interface{}:
m[keyName] = &keyValue
// create a similar returned map from the child node and add it to the current return map
child := BuildExpressionAttributeNames(t)
for kk, vv := range child {
m[kk] = vv
}
}
}
return m
}
func BuildUpdateExpression(input map[string]interface{}, parentName, exprParentName string) string {
var s []string
for k, v := range input {
keyName := strings.Join([]string{":", exprParentName, strings.ToLower(k)}, "")
k = fmt.Sprintf("#%v", strings.ToLower(k))
// add more type cases if more are added to the struct
switch t := v.(type) {
case string, bool, int:
currAttrName := k
if len(parentName) > 0 {
currAttrName = strings.Join([]string{parentName, currAttrName}, ".")
}
s = append(s, strings.Join([]string{currAttrName, "=", keyName}, " "))
case map[string]interface{}:
nextExprParentName := strings.TrimPrefix(keyName, ":")
nextParentName := k
if len(parentName) > 0 {
nextParentName = strings.Join([]string{parentName, k}, ".")
}
s = append(s, BuildUpdateExpression(t, nextParentName, nextExprParentName))
}
}
return strings.Join(s, ", ")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment