Skip to content

Commit

Permalink
service/dynamodb/dynamodbattribute: Add UnmarshalListOfMaps
Browse files Browse the repository at this point in the history
Adds support for unmarshaling a list of maps. This is useful for
unmarshaling the DynamoDB AttributeValue list of maps returned by APIs
like Query and Scan.

Example can be found in example/service/dynamodb/scanItems.

Fix aws#810, aws#696
  • Loading branch information
jasdel committed Oct 18, 2016
1 parent 7557129 commit 5c448e4
Show file tree
Hide file tree
Showing 4 changed files with 205 additions and 0 deletions.
34 changes: 34 additions & 0 deletions example/service/dynamodb/scanItems/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Example

`scanItems` is an example how to use Amazon DynamoDB's Scan API operation with the SDK's `dynamodbattributes.UnmarshalListOfMaps` to unmarshal the Scan response's `Items` `[]map[string]*dynamodb.AttributeValue` field. This unmarshaler can be used with all `[]map[string]*dynamodb.AttributeValue` type fields.

## Go Type

The `Item` time will be used by the example to unmarshal the DynamoDB table's items to.

```go
type Item struct {
Key int
Desc string
Data map[string]interface{}
}
```

## Usage

`scanItems.go -table "<table_name>" -region "<optional_region>"`

## Output

```
0: Key: 123, Desc: An item in the DynamoDB table
Num Data Values: 0
1: Key: 2, Desc: Second ddb item
Num Data Values: 2
- "A Field": 123
- "Another Field": abc
2: Key: 1, Desc: First ddb item
Num Data Values: 2
- "Value 1": abc
- "Value 2": 666
```
102 changes: 102 additions & 0 deletions example/service/dynamodb/scanItems/scanItems.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// +build example

package main

import (
"flag"
"fmt"
"os"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
)

func exitWithError(err error) {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}

func main() {
cfg := Config{}
if err := cfg.Load(); err != nil {
exitWithError(fmt.Errorf("failed to load config, %v", err))
}

// Create the config specifiing the Region for the DynamoDB table.
// If Config.Region is not set the region must come from the shared
// config or AWS_REGION environment variable.
awscfg := &aws.Config{}
if len(cfg.Region) > 0 {
awscfg.WithRegion(cfg.Region)
}

// Create the session that the DynamoDB service will use.
sess, err := session.NewSession(awscfg)
if err != nil {
exitWithError(fmt.Errorf("failed to create session, %v", err))
}

// Create the DynamoDB service client to make the query request with.
svc := dynamodb.New(sess)

// Build the query input parameters
params := &dynamodb.ScanInput{
TableName: aws.String(cfg.Table),
}
if cfg.Limit > 0 {
params.Limit = aws.Int64(cfg.Limit)
}

// Make the DynamoDB Query API call
result, err := svc.Scan(params)
if err != nil {
exitWithError(fmt.Errorf("failed to make Query API call, %v", err))
}

items := []Item{}

// Unmarshal the Items field in the result value to the Item Go type.
err = dynamodbattribute.UnmarshalListOfMaps(result.Items, &items)
if err != nil {
exitWithError(fmt.Errorf("failed to unmarshal Query result items, %v", err))
}

// Print out the items returned
for i, item := range items {
fmt.Printf("%d: Key: %d, Desc: %s\n", i, item.Key, item.Desc)
fmt.Printf("\tNum Data Values: %d\n", len(item.Data))
for k, v := range item.Data {
fmt.Printf("\t- %q: %v\n", k, v)
}
}
}

type Item struct {
Key int
Desc string
Data map[string]interface{}
}

type Config struct {
Table string // required
Region string // optional
Limit int64 // optional

}

func (c *Config) Load() error {
// fs := flag.NewFlagSet("QueryItem config", flag.ExitOnError)
flag.Int64Var(&c.Limit, "limit", 0, "Limit is the max items to be returned, 0 is no limit")
flag.StringVar(&c.Table, "table", "", "Table to Query on")
flag.StringVar(&c.Region, "region", "", "AWS Region the table is in")
flag.Parse()

if len(c.Table) == 0 {
flag.PrintDefaults()
return fmt.Errorf("table name is required.")
}

return nil
}
16 changes: 16 additions & 0 deletions service/dynamodb/dynamodbattribute/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,22 @@ func UnmarshalList(l []*dynamodb.AttributeValue, out interface{}) error {
return NewDecoder().Decode(&dynamodb.AttributeValue{L: l}, out)
}

// UnmarshalListOfMaps is an alias for Unmarshal func which unmarshals a
// slice of maps of attribute values.
//
// This is useful for when you need to unmarshal the Items from a DynamoDB
// Query API call.
//
// The output value provided must be a non-nil pointer
func UnmarshalListOfMaps(l []map[string]*dynamodb.AttributeValue, out interface{}) error {
items := make([]*dynamodb.AttributeValue, len(l))
for i, m := range l {
items[i] = &dynamodb.AttributeValue{M: m}
}

return UnmarshalList(items, out)
}

// A Decoder provides unmarshaling AttributeValues to Go value types.
type Decoder struct {
MarshalOptions
Expand Down
53 changes: 53 additions & 0 deletions service/dynamodb/dynamodbattribute/decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,59 @@ func TestUnmarshalMapError(t *testing.T) {
}
}

func TestUnmarshalListOfMaps(t *testing.T) {
type testItem struct {
Value string
Value2 int
}

cases := []struct {
in []map[string]*dynamodb.AttributeValue
actual, expected interface{}
err error
}{
{ // Simple map conversion.
in: []map[string]*dynamodb.AttributeValue{
{
"Value": &dynamodb.AttributeValue{
BOOL: aws.Bool(true),
},
},
},
actual: &[]map[string]interface{}{},
expected: []map[string]interface{}{
{
"Value": true,
},
},
},
{ // attribute to struct.
in: []map[string]*dynamodb.AttributeValue{
{
"Value": &dynamodb.AttributeValue{
S: aws.String("abc"),
},
"Value2": &dynamodb.AttributeValue{
N: aws.String("123"),
},
},
},
actual: &[]testItem{},
expected: []testItem{
{
Value: "abc",
Value2: 123,
},
},
},
}

for i, c := range cases {
err := UnmarshalListOfMaps(c.in, c.actual)
assertConvertTest(t, i, c.actual, c.expected, err, c.err)
}
}

type unmarshalUnmarshaler struct {
Value string
Value2 int
Expand Down

0 comments on commit 5c448e4

Please sign in to comment.