Is it possible to convert a DynamoDB AttributeMap type into an interface?

Assume I define a TypeScript interface like this:

interface IPerson {
    id: string,
    name: string
}

If I perform a table scan on the 'persons' table in DynamoDB, my goal is to achieve the following:

const client = new AWS.DynamoDB.DocumentClient();

client.scan(params, (error, result) => {
    const people: IPerson[] = result.Items as IPerson[];
};

An error stating

Type 'AttributeMap[]' cannot be converted to type 'IPerson[]'
is thrown.

Although they are different types, the data structure remains identical. How can I cast the DynamoDB AttributeMap to conform to my IPerson interface?

Answer №1

To tackle this task effectively, rely on the AWS DynamoDB SDK's unmarshall method.


Utilizing JavaScript AWS SDK V3 (after December 2020)

You can utilize the unmarshall method from the @aws-sdk/util-dynamodb package.

Check out an example in the documentation.

const { unmarshall } = require("@aws-sdk/util-dynamodb");

unmarshall(res.Item) as Type;

Note: The AWS DynamoDB JavaScript SDK offers a DynamoDBDocumentClient, simplifying the process by using standard key-value objects.


The earlier version of the JavaScript AWS SDK (prior to December 2020)

Refer to AWS.DynamoDB.Converter:

// Cast the result items to a type.
const people: IPerson[] = result.Items?.map((item) => Converter.unmarshall(item) as IPerson);

Documentation for unmarshall():

unmarshall(data, options) ⇒ map

Transform a DynamoDB record into a JavaScript object.

Note: The AWS DynamoDB JavaScript SDK provides a DynamoDBDocumentClient, streamlining the process with conventional key-value objects.

Answer №2

After some trial and error, I was able to resolve the issue by first converting it to type unknown:

const individuals: IPerson[] = data.Results as unknown as IPerson[];

Answer №3

Enhance the IPerson interface by incorporating AttributeMap as shown below:

interface IPerson extends AttributeMap {
    id: string,
    name: string
}

Answer №4

It appears that the aws-sdk does not have a built-in serialization feature.

My approach so far has been as follows, and it's been working smoothly:

interface IPerson {
    id: string,
    name: string
}

// for individual objects
const person = result?.Item as IPerson;

// for arrays
const people = result?.Items?.map((item) => item as IPerson);

Answer №5

I encountered a similar issue, but my code was utilizing get instead of scan. To resolve this issue, I employed a type guard known as a type predicate. My setup involves using version 2 of the AWS SDK and employing an object of type AWS.DynamoDB.DocumentClient for reading from the DynamoDB table. Initially, I attempted to utilize unmarshall following suggestions in Steven's response, but it resulted in an empty object. It appeared that the client I utilized was already handling the unmarshalling process.

In essence, the problem boils down to a common scenario: having an object of type any and aiming to convert it into a specific type like IPerson. A typical approach is to employ a type predicate. The implementation may look something like this:

function isPerson(obj: any): obj is IPerson{
    if(!obj){
        return false
    }
    if(!isSimpleProperty(obj, "id") || !isString(obj.id)){
        return false
    }
    if(!isSimpleProperty(obj, "name") || !isString(obj.name)){
        return false
    }
    return true
}

function isString(obj: any){
    return (typeof obj === 'string' || obj instanceof String)
}

function isSimpleProperty(obj: any, property: PropertyKey){
    const desc = Object.getOwnPropertyDescriptor(obj, property)
    if(!!desc){
        return (!desc.get && !desc.set)
    }
    return false
}

Following this, you can use a filter method to retrieve all desired items of the correct type:

const client = new AWS.DynamoDB.DocumentClient();

client.scan(params, (error, result) => {
    const people: IPerson[] | undefined = result.Items?.filter(isPerson)
});

Answer №6

It is unfortunate that the aws-sdk library does not offer a Generic interface for setting the response type directly. This means you cannot simply do something like:

const person = await client.get<IPerson>(params).promise();

As mentioned by others, one workaround is to cast the type using:

const person = result?.Item as IPerson;

However, there is a risk with this approach as result?.Item may not necessarily be of type IPerson, and it involves tricking TypeScript into thinking it is.

Due to DynamoDB's schemaless nature, the item in your database could have any shape, potentially missing the required id and name attributes. It is essential to implement checks to handle unexpected data.

An effective method is to utilize a formatter that transforms your DynamoDB object into the desired response:

const client = new AWS.DynamoDB.DocumentClient();

interface IPerson {
  id?: string,
  name?: string
}

const format = (data: DynamoDB.DocumentClient.AttributeMap): IPerson {
  return {
    id: data?.id,
    name: data?.name
  };
} 

const person = await client.get(params).promise();
      
const response = format(basket.Item); // maps DynamoDB.DocumentClient.AttributeMap => IPerson

DynamoDB.DocumentClient.AttributeMap
represents the response type of client.get, while other client methods such as scan may have different response types. Nevertheless, the concept remains consistent - mapping the client response to the required type.

Answer №7

One approach is to leverage the functionality provided by class-transformer. By using annotations like @Exclude() for properties you wish to hide, you can utilize the plainToClass function. Installation is straightforward, as you can easily set it up on your local system or in a node project with the help of the Node Package Manager:

npm install class-transformer
interface IPerson {
    id: string,
    name: string
    @Exclude()
    password: string
}

// converting an object
const person = plainToClass(IPerson, response.Item)

// converting an array
const people = response.Items.map((item) => plainToClass(IPerson, item));

Another option is to make use of Object.assign for a simpler solution:

interface IPerson {
    id: string,
    name: string
}

// converting an object
const person = Object.assign(IPerson, response.Item);

// converting an array
const people = response.Items.map((item) => Object.assign(IPerson), item);

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Error: Android package not found when building a NativeScript app with TypeScript

I encountered the following error message: main-page.ts(15,26): error TS2304: Cannot find name 'android'. This error occurred after setting up a new NativeScript project using TypeScript. tns create demo --template typescript I then added the ...

What return type should be used when returning `_.orderBy` from a TypeScript function?

The current packages I have installed are lodash and @types/lodash. In my code, I am using: import _ from 'lodash'; function doSomething(): string[] { const letters = ['c', 'a', 'b']; return _.orderBy(letters ...

Switching Theme Dynamically in a Multi-tenant Next.js + Tailwind App

I'm currently developing a Next.js + Tailwind application that supports multiple tenants and allows each tenant to easily switch styles or themes. I've been struggling with the idea of how to implement this feature without requiring a rebuild of ...

Migration of old AngularJS to TypeScript in require.js does not recognize import statements

I am looking to transition my aging AngularJS application from JavaScript to TypeScript. To load the necessary components, I am currently utilizing require.js. In order to maintain compatibility with scripts that do not use require.js, I have opted for usi ...

How come the path alias I defined is not being recognized?

Summary: I am encountering error TS2307 while trying to import a file using an alias path configured in tsconfig.json, despite believing the path is correct. The structure of directories in my Angular/nx/TypeScript project appears as follows: project |- ...

The resource in CosmosDB cannot be found

I have successfully stored documents on Cosmos, but I am encountering an issue when trying to retrieve them using the "read" method. this.cosmos = new CosmosClient({ endpoint: '' key: '' }); this.partitionKey = '/id' thi ...

How can I retrieve a password entered in a Material UI Textfield?

My code is functioning properly, but I would like to enhance it by adding an option for users to view the password they are typing. Is there a way to implement this feature? const [email, setEmail] = useState(''); const [password, setPassword] = ...

Angular 2 - retrieve the most recent 5 entries from the database

Is there a way to retrieve the last 5 records from a database? logs.component.html <table class="table table-striped table-bordered"> <thead> <tr> <th>Date</th> <th>Logging ...

I continuously encounter an issue in Vite version 3.2.4 where an error pops up stating `[vite:esbuild] The service has stopped running: write EPIPE`

When I finished creating a Vite app, I ran the command npm run dev and encountered the following error: [vite:esbuild] The service is no longer running: write EPIPE https://i.stack.imgur.com/MZuyK.png I need help solving this error. Can anyone provide gu ...

What are the steps to customize the collapse and expand icons?

Having trouble changing the icon up and down when collapsing and expanding with the code below. <div class="attach-link"> <a href="javascript:void(0);" *ngIf="fileData.fileDataType.canAttach && !isFinancialEntity" (click) ...

Singleton constructor running repeatedly in NextJS 13 middleware

I'm encountering an issue with a simple singleton called Paths: export default class Paths { private static _instance: Paths; private constructor() { console.log('paths constructor'); } public static get Instance() { consol ...

Animation scaling on the iPhone seems to abruptly jump away once it finishes

Encountering an issue with animations that is causing unexpected behavior on physical iPhone devices but not in the simulators. The problem arises when the background zooms in and then the div suddenly jumps to the left after the animation completes, as sh ...

Combine all TypeScript enums into a single one

Looking for a way to combine two separate enums into one for easier use. export enum ActionTypes1 { ClearError = 'CLEAR_ERROR', PrependError = 'PREPEND_ERROR', } export enum ActionTypes2 { IncrementCounter = 'INCREMENT_COUNT ...

Exploring the correct navigation of page objects through Protractor using TypeScript

I'm working on setting up a protractor test suite with TypeScript and running into an issue involving chaining of pageObjects for multiple pages. I haven't seen any examples that deal with this specific scenario. I've simplified the example ...

Is there a way to determine if silent login is feasible with adaljs-angular4?

I'm currently utilizing the library [email protected] for an Angular 6 project. I am attempting to achieve the following: If a silent login (login without requiring user input) with Office365 is achievable, then perform a silent login (using ...

Utilize multiple validators.patterns within a single value for enhanced data validation

I need to implement two different patterns for the formControlName='value' based on the type selected. If type is 'A', I want to use the valuePattern, and if type is 'B', I want to use the uname pattern. This is my HTML code: ...

How can data be transferred from a parent to a child component in Angular?

I'm facing an issue trying to pass the selected value from a dropdownlist in a user interface. I have a parent component (app.component.html) and a child component (hello.component.html & hello.component.ts). My goal is to transfer the option val ...

The Material-UI TextField is placed in the Header section of the page

As I scroll down the page, I noticed that the TextField in the header area remains visible. This header is styled using CSS and includes a TextField from the material-ui library. This is my first time creating a header with CSS. Are there any specific cod ...

A guide on transforming a Firebase Snapshot child into a personalized object in a React Native environment

I'm looking for a way to retrieve data from Firebase Realtime Database and display it in FlatList format. What is the most efficient method for extracting the child value and converting it into a custom object? For example: class CustomObject { ...

Quickest method for sorting an array of objects based on the property of another object's array

Within my code, I have two arrays of objects, both containing a "columnId" property. My goal is to rearrange the first array to match the order of the second. I attempted the following method: filtered = visibleColumns.filter(function(v) { re ...