AWS: Reformatting DynamoDB Data

Intro

One of the quirks of DynamoDB that we learned earlier is that all of the data in Dynamo DB is stored with attribute values.

πŸ“˜ DynamoDB Attribute Values

for example

{
    "Items": [
        {
            "UserId": {
                "S": "user002"
            },
            "income": {
                "N": "100000"
            },
            "height": {
                "N": "150"
            },
            "age": {
                "N": "33"
            }
        }
    ]
}

With N and S indicating string and number. And this gives us a bit of a problem when we get this data returned to us, we need to convert it back into a usable format.

There are a couple of ways to do this. First of all, Amazon has released a native tool in the SDK to handle converting data both into, and out of the DynamoDB called Document Client.

πŸ“˜ AWS Document Client

Another way to handle this would be to do it manually. Let’s take a look at each method.

Manual Reformat

Let us change our Lambda function to the following

Lambda/cyGetData/index.js
// Import the AWS SDK
var AWS = require("aws-sdk")
var dynamodb = new AWS.DynamoDB({
  region: "us-east-2",
  apiVersion: "2012-08-10",
})

exports.handler = (event, context, callback) => {
  const type = event.type

  // app will request all user data or single user data
  if (type === "all") {
    var params = {
      TableName: "compare-yourself",
    }

    dynamodb.scan(params, function (err, data) {
      if (err) {
        console.log(err, err.stack) // an error occurred
      } else {
        console.log(data) // successful response

        // map returned data to new variable
        const items = data.Items.map(item => {          return {            UserId: String(item.UserId.S),            age: Number(item.age.N),            height: Number(item.height.N),            income: Number(item.income.N),          }        })
        // callback that new variable
        callback(null, items)      }
    })
  } else if (type === "single") {
    callback(null, "Requested Single User Data")
  } else {
    callback(null, "Invalid Type Request in URL Parameter")
  }
}

Where we have simply created a new object from a map of the originally returned object, and then put that new object in the callback. This successfully returns the following.

result
[
  {
    "UserId": "user002",
    "age": 33,
    "height": 150,
    "income": 100000
  },
  {
    "UserId": "user001",
    "age": 33,
    "height": 150,
    "income": 100000
  },
  {
    "UserId": "user003",
    "age": 55,
    "height": 150,
    "income": 5670000
  }
]

Document Client Reformat

With this method we use the Document Client to abstract away the DynamoDB attribute values, and we can use it while sending and receiving data from the database.

πŸ“˜ AWS Document Client

Lambda/cyGetData/index.js
// Import the AWS SDK
var AWS = require("aws-sdk")
var docClient = new AWS.DynamoDB.DocumentClient({ region: "us-east-2" })
exports.handler = (event, context, callback) => {
  const type = event.type

  // app will request all user data or single user data
  if (type === "all") {
    var params = {
      TableName: "compare-yourself",
    }

    docClient.scan(params, function (err, data) {      if (err) {
        // an error occurred
        console.log(err, err.stack)
      } else {
        // successful response
        console.log(data)

        callback(null, data)
      }
    })
  } else if (type === "single") {
    callback(null, "Requested Single User Data")
  } else {
    callback(null, "Invalid Type Request in URL Parameter")
  }
}

which successfully returns to us

{
  "Items": [
    {
      "UserId": "user002",
      "income": 100000,
      "height": 150,
      "age": 33
    },
    {
      "UserId": "user001",
      "income": 100000,
      "height": 150,
      "age": 33
    },
    {
      "UserId": "user003",
      "income": 5670000,
      "height": 150,
      "age": 55
    }
  ],
  "Count": 3,
  "ScannedCount": 3
}

Conclusion

Using the document client would be my preferred method, but it’s good to keep in mind that we can do this all manually if we have an edge use case that requires manual conversion.