12

Can anyone explain how to use GetItemInput type when calling DocumentClient.get ?

If I pass in an object of any type get works but if I try and strongly type the params object I get this error:

ValidationException: The provided key element does not match the schema

Here is my lambda function code where I pass the params as type any:

export const get: Handler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {

  console.log(event.pathParameters)
  if (!event.pathParameters) {
    throw Error("no path params")
  }

  const params: any = {
    Key: {
      id: event.pathParameters.id
    },
    TableName: table
  }

  console.log(params)
  try {
    const result: any = await dynamoDb.get(params).promise()
    return {
      body: JSON.stringify(result.Item),
      statusCode: result.$response.httpResponse.statusCode
    }

  } catch (error) {
    console.log(error)
    return {
      body: JSON.stringify({
        message: `Failed to get project with id: ${event.pathParameters!.id}`
      }),
      statusCode: 500
    }
  }
}

And here is my attempt to get it to work with type GetItemInput

export const get: Handler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {

  console.log(event.pathParameters)
  if (!event.pathParameters) {
    throw Error("no path params")
  }

  const params: GetItemInput = {
    Key: {
      "id": { S: event.pathParameters.id }
    },
    TableName: table
  }

  console.log(params)
  try {
    const result: any = await dynamoDb.get(params).promise()
    return {
      body: JSON.stringify(result.Item),
      statusCode: result.$response.httpResponse.statusCode
    }

  } catch (error) {
    console.log(error)
    return {
      body: JSON.stringify({
        message: `Failed to get project with id: ${event.pathParameters!.id}`
      }),
      statusCode: 500
    }
  }
}

If I leave the Key as before ala:

const params: GetItemInput = {
  Key: {
    id: event.pathParameters.id
  },
  TableName: table
}

Unsurprisingly I get a type error. But can't fathom how I can form my Key such that I dont get the ValidationException.

Note the id field is of type String in the DynamoDB.

Mickael B.
  • 4,755
  • 4
  • 24
  • 48
Mingo
  • 836
  • 3
  • 12
  • 19
  • Where did you find GetItemInput? It doesn't look like part of the JS SDK to me. I can see its part of the Ruby and Go SDK. – F_SO_K Mar 14 '19 at 16:29
  • this is my import statement which works so presume its in the dynamo-db typings import { AttributeValue, GetItemInput, ScanInput, StringAttributeValue } from "aws-sdk/clients/dynamodb" https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#get-property – Mingo Mar 14 '19 at 16:30
  • That link suggests this should be valid ... const params: GetItemInput = { Key: { "id": event.pathParameters.id }, TableName: table }, however i get a type error saying "type 'string' has no attributes in common with type 'AttributeValue'" – Mingo Mar 14 '19 at 16:35
  • I can't see any reference to that in the link? – F_SO_K Mar 14 '19 at 16:37
  • The SDK specifically abstracts away attribute values and uses native JSON object instead (see top of the page you linked). I could be wrong but I don't think what you are trying is possible. – F_SO_K Mar 14 '19 at 16:38
  • so i shouldnt be using the GetItemInput type ? Why have it in the method signature if you cant use it. If i import the types and then hover over the method, intellisense is telling me it takes as argument a GetItemInput, yet there seems to be no way to construct one that actually works, all i am trying to do is use the types that the method says it takes in its signature – Mingo Mar 14 '19 at 16:40

3 Answers3

35

I think you mix two different client definition files DynamoDB and DynamoDB.DocumentClient. While you're using the DynamoDB.DocumentClient client, at the same time you're using the interface DynamoDB.Types.GetItemInput from DynamoDB.

You should use DynamoDB.DocumentClient.GetItemInput:

import {DynamoDB} from 'aws-sdk';
const dynamo = new DynamoDB.DocumentClient({apiVersion: '2012-08-10'});

...
const params: DynamoDB.DocumentClient.GetItemInput = {
    TableName: table,
    Key: {
        id: event.pathParameters.id
    }
};
const result = await this.dynamo.get(params).promise();
Mickael B.
  • 4,755
  • 4
  • 24
  • 48
ttulka
  • 10,309
  • 7
  • 41
  • 52
7

@ttulka's answer is perfect, but just to add on that I had the same issue and it really helped to spend 5 minutes to disambiguate the now MANY different ways of accessing DynamoDB from official AWS JS SDKs.

Reading this for 5 minutes is my answer, and it will all become clear to you after this;

https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/modules/_aws_sdk_lib_dynamodb.html

The tldr; is;

  • know which AWS JS version you're using, whether its v2, or v3, and be mindful when you pull other samples from the internet that they might be applying to older versions - you should be able to tell based on the imports you're using, here are what the v3 imports look like:

import { DynamoDB } from "@aws-sdk/client-dynamodb"; // ES6 import
import { DynamoDBDocument, PutCommandInput } from "@aws-sdk/lib-dynamodb"; // ES6 import
  • you likely want to use DocumentClient instead of raw DynamoDB where you need to specify the raw types of all attributes - in order to be successful doing this, your DynamoDB JS objects must be constructed in the right way (and the doc above explains exactly how)
  • know if you're using the "bare bones" or the "full version" of DynamoDB, as you'll have access to more types/hints in TS if you're using the full version
  • and you likely want to use client-dynamodb AND lib-dynanmodb, as the latter is an extra to the SDK to help make things easy (and it does)
mobob
  • 849
  • 10
  • 17
  • 1
    "they might be applying to older versions" -- this is massively important when dealing with AWS SDKs. Their docs are routinely inadequate when it comes to explaining this. – jcollum Apr 13 '22 at 00:03
2

DynamoDbClient

For anyone like me whos too lazy to make their own dynamoDB client and wants to copy and paste one here's some code I wrote:

import { AttributeValue, DynamoDB, GetItemInput, PutItemCommandOutput, PutItemInput, QueryCommandInput } from "@aws-sdk/client-dynamodb";
import { marshall, unmarshall } from "@aws-sdk/util-dynamodb";

export default class DynamoDbClient {
    dynamoDB: DynamoDB;
    tableName: string;
    constructor(tableName: string) {
        this.dynamoDB = new DynamoDB({});
        this.tableName = tableName
    }

    async getItem<T>(inputKeys: DynamoDbClientGetItemInput): Promise<T | UnmarshalledAny> {
        const params: GetItemInput = {
            TableName: this.tableName,
            Key: marshall(inputKeys)
        };
        const result = await this.dynamoDB.getItem(params);
        if (!result || !result.Item) {
            return null
        }
        return unmarshall(result.Item)
    }

    async query<T>(input: Partial<QueryCommandInput>): Promise<T | UnmarshalledAny> {
        const params: QueryCommandInput = {
            TableName: this.tableName,
            ...input
        };
        const result = await this.dynamoDB.query(params);
        if (!result.Items) {
            return []
        }
        return this.unmarshallList(result.Items)
    }

    unmarshallList(items: MarshalledItem[]) {
        const unmarshalledItems = []
        for (let index = 0; index < items.length; index++) {
            const item = items[index];
            unmarshalledItems.push(unmarshall(item))
        }
        return unmarshalledItems
    }

    async putItem(inputItem: DynamoDbClientPutItemInput): Promise<PutItemCommandOutput> {
        const params: PutItemInput = {
            TableName: this.tableName,
            Item: marshall(inputItem, { removeUndefinedValues: true })
        };
        return await this.dynamoDB.putItem(params);
    }
}

export interface DynamoDbClientGetItemInput { id: string, pk: string }
export interface DynamoDbClientPutItemInput { id: string, pk: string, data: any }

export interface UnmarshalledAny {
    [key: string]: any;
}

export interface MarshalledItem {
    [key: string]: AttributeValue;
}

You can install the libs with:

npm i @aws-sdk/client-dynamodb @aws-sdk/util-dynamodb

You'll need to change your key names to whatever is in your dynamoDB, mine are called pk and id

Ali Fensome
  • 440
  • 4
  • 7
  • 1
    there is an official package from AWS doing similar things but more comprehensively. https://www.npmjs.com/package/@aws-sdk/lib-dynamodb – Kennith May 06 '22 at 10:18