Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Retrieving a StringSet value using DynamoDB.DocumentClient in Node

The DynamoDB.DocumentClient automatically marshals & unmarshals values between JavaScript types DynamoDB's more descriptive AttributeMap type. However, when working with an Item that has a StringSet attribute, it does not seem to do the conversion automatically.

When adding a StringSet attribute to the table using DocumentClient, I use the createSet(...) method to convert the array to a Set. When retrieving the value back, what is the inverse of createSet(...)? Is the best practice to just access the Set's .values directly? And if so, is that documented somewhere?

Here's sample code adding an Item with a StringSet attribute, then retrieving that item:

    const docClient = new DocumentClient();
    const TableName = "StringSets-Example";
    const PK = "Names";
    const Values = ["Peter", "Paul", "Mary"];

    const putParams = {
        TableName,
        Item: {
            PK,
            names: docClient.createSet(Values)
        }
    }
    await docClient.put(putParams).promise();

    // ... some time later, I can retrieve the value with ...

    const getParams = {
        TableName,
        Key: { PK }
    }
    const result = await docClient.get(getParams).promise();

The result.Item there is a Set object, whereas I would expect it to be the same array I passed into createSet(...).

If interested in seeing this live, this repo has a fully-functioning example. Clone it, npm install, and run index.js and you'll see something like:

 $ ./index.js 
Running On: darwin 19.6.0
Node version: v12.20.0
AWS SDK version: 2.799.0
-------------------------
Creating table "StringSets-Example"
Waiting for "StringSets-Example" status to be "ACTIVE"
  Table status is: CREATING
  Table status is: ACTIVE
Put String Set "["Peter, "Paul, "Mary"]" into "StringSets-Example" with key "Names" and attribute "names"
Retrieved Item with key "Names" from "StringSets-Example"
The raw Item:  {
  PK: 'Names',
  names: Set {
    wrapperName: 'Set',
    values: [ 'Mary', 'Paul', 'Peter' ],
    type: 'String'
  }
}
The raw Item.names.values: [ 'Mary', 'Paul', 'Peter' ]
-------------------------
Done.  To clean up, run:
    ./src/deleteTable.js
like image 557
Peter Wagener Avatar asked Nov 29 '20 17:11

Peter Wagener


1 Answers

The best solution I have here is to avoid the DocumentClient and the createSet(...) method. Here's a sample using AWS SDK V3:

const key = { PK: `SampleNames`, SK: `SampleNames` };
const names = new Set([`Peter`, `Paul`, `Mary`]);
const item = { ...key, names };
const marshalledItem = marshall(item);

console.log(`Raw item: ${inspect(item)}`)
console.log(`Marshalled item to PUT: ${inspect(marshalledItem, { depth: 4 })}`)
const client = new DynamoDBClient({});
await client.send(new PutItemCommand({
    TableName: tableName,
    Item: marshalledItem,
}));

const { Item } = await client.send(new GetItemCommand({
    TableName: tableName,
    Key: marshall(key),
}));

console.log(`Returned item: ${inspect(Item, { depth: 4 })}`);
console.log(`Unmarshalled returned item: ${inspect(unmarshall(Item))}`);

The console output from there is:

Raw item: {
  PK: 'SampleNames',
  SK: 'SampleNames',
  names: Set { 'Peter', 'Paul', 'Mary' }
}
Marshalled item to PUT: {
  PK: { S: 'SampleNames' },
  SK: { S: 'SampleNames' },
  names: { SS: [ 'Peter', 'Paul', 'Mary' ] }
}
Returned item: {
  PK: { S: 'SampleNames' },
  SK: { S: 'SampleNames' },
  names: { SS: [ 'Mary', 'Paul', 'Peter' ] }
}
Unmarshalled returned item: {
  PK: 'SampleNames',
  SK: 'SampleNames',
  names: Set { 'Mary', 'Paul', 'Peter' }
}

... which makes a lot more sense to me. I expect using the marshall/unmarshall methods from AWS SDK V2's Converter module would work similarly.

like image 71
Peter Wagener Avatar answered Oct 08 '22 21:10

Peter Wagener