DynamoDB and Codable
Writing generic code while interacting with DynamoDB can be quite hard. For every model you want to upload to or download from DynamoDB, you need custom code for the conversion from model to AttributeValue
dictionary and vice versa. To ease this process and remove the requirement for custom code, the DynamoDB library has a Codable
extension. This is split into two parts: the encoder/decoder and custom versions of some of the key functions.
Encoder and Decoder
The DynamoDB Codable
extension comes with both an encoder, DynamoDBEncoder
, which takes a Codable and generates an AttributeValue
dictionary, and a decoder DynamoDBDecoder
, which takes an AttributeValue
dictionary and generates a Codable object. Imagine you have the following type:
struct Person: Codable {
let id: Int
let name: String
let age: Int
}
The following code generates an AttributeValue
dictionary which can be used in a DynamoDB operation:
let person = Person(id: 1, name: "John Smith", age: 35)
let personAttributes = try DynamoDBEncoder().encode(person)
The contents of personAttributes
is as follows
["id": .n("1"), "name": .s("John Smith"), "age": .n("35")]
You can then use DynamoDBDecoder
to convert back to your original Person
type.
let person2 = try DynamoDBDecoder().decode(Person.self, from: personAttributes)
Custom Codable functions
Now you have an Encoder
and Decoder
to move between Codable
objects and AttributeValue
dictionaries, you can generate the data in the required format for DynamoDB and parse the results sent back. The DynamoDB library extension gives you a little more help though. It implements custom versions of the most commonly used functions which take a Codable
object instead of a AttributeValue
dictionary. So the following code
let input = DynamoDB.PutItemInput(
item: [
"id": .n(person.id.description),
"name": .s(person.name),
"age": .n(person.age.description)
],
tableName: "my-table"
)
let output = try await dynamoDB.putItem(input)
can be reduced to
let input = DynamoDB.PutItemCodableInput(item: person, tableName: "my-table")
let output = try await dynamoDB.putItem(input)
Similarly when using DynamoDB.getItem(_:)
, the code below which parses an AttributeValue
dictionary returned
let input = DynamoDB.GetItemInput(key: ["id": .n("1")], tableName: "my-table")
let output = try await Self.dynamoDB.getItem(input)
guard case .n(let idString) = output.item?["id"],
case .s(let name) = output.item?["name"],
case .n(let ageString) = output.item?["age"],
let id = Int(idString),
let age = Int(ageString) else { throw SomeError()}
let person = Person(id: id, name: name, age: age)
can be reduced to
let input = DynamoDB.GetItemInput(key: ["id": .n("1")], tableName: "my-table")
let output = try await Self.dynamoDB.getItem(input, type: Person.self)
let person = output2.item
There are custom Codable
versions of putItem
, getItem
, query
, scan
and updateItem
provided. There are also paginator versions of the query
and scan
functions supplied.