假设我有一个打字稿界面:
interface IPerson {
id: string,
name: string
}
我在 dynamo 中的人员表上运行表扫描,我想要做的是:
const client = new AWS.DynamoDB.DocumentClient();
client.scan(params, (error, result) => {
const people: IPerson[] = result.Items as IPerson[];
};
我收到错误
Type 'AttributeMap[]' cannot be converted to type 'IPerson[]'
显然它们是不同的类型,但数据结构完全相同。我的问题是如何才能将发电机
AttributeMap
投射到我的 IPerson
界面?
执行此操作的正确方法是使用 AWS DynamoDB SDK
unmarshall
方法。
JavaScript AWS SDK V3(2020 年 12 月后)
使用
unmarshall
包中的 @aws-sdk/util-dynamodb
方法。
文档示例.
const { unmarshall } = require("@aws-sdk/util-dynamodb");
unmarshall(res.Item) as Type;
旁注:AWS DynamoDB JavaScript SDK 提供了
DynamoDBDocumentClient
,它消除了整个问题并使用普通的键值对象。
JavaScript AWS SDK 的先前版本(2020 年 12 月之前)
AWS.DynamoDB.Converter
:
// Cast the result items to a type.
const people: IPerson[] = result.Items?.map((item) => Converter.unmarshall(item) as IPerson);
unmarshall()
的文档:
解组(数据,选项)⇒映射
将 DynamoDB 记录转换为 JavaScript 对象。
旁注:AWS DynamoDB JavaScript SDK 提供了
DynamoDBDocumentClient
,它消除了整个问题并使用普通的键值对象。
我刚刚通过先转换为
unknown
来解决它:
const people: IPerson[] = result.Items as unknown as IPerson[];
使用 AttributeMap 扩展 IPerson 接口,如下所示:
interface IPerson extends AttributeMap {
id: string,
name: string
}
似乎
aws-sdk
没有内置序列化。
到目前为止我所做的如下,效果很好:
interface IPerson {
id: string,
name: string
}
// for single object
const person = result?.Item as IPerson;
// for arrays
const people = result?.Items?.map((item) => item as IPerson);
我也遇到了类似的问题,但使用的是
get
而不是 scan
。我使用类型保护(又名类型谓词)解决了这个问题。我正在使用 AWS SDK v2,并使用 AWS.DynamoDB.DocumentClient
类型的对象从 DynamoDB 表中读取。我首先尝试按照Steven的回答中的建议使用unmarshall,但这导致了一个空对象。看来我使用的客户端已经为我解组了对象。
在这种情况下,问题简化为标准问题:您有一个类型为
any
的对象,并且您想将其转换为某种类型 IPerson
。执行此操作的标准方法是使用类型谓词。像这样的东西:
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
}
然后您可以使用过滤器方法来取回所有您想要的正确类型的项目:
const client = new AWS.DynamoDB.DocumentClient();
client.scan(params, (error, result) => {
const people: IPerson[] | undefined = result.Items?.filter(isPerson)
});
不幸的是,
aws-sdk
库没有提供通用接口供您设置响应类型。所以你不能做这样的事情:
const person = await client.get<IPerson>(params).promise();
正如其他人提到的,您可以使用类似的方法来转换类型:
const person = result?.Item as IPerson;
这样做的风险是
result?.Item
可能不是 IPerson
的类型,而你正在欺骗 Typescript 认为它是。
由于 DynamoDB 的无模式性质,数据库中的项目在技术上可以是任何形状,因此它可能不包含您所需的
id
和 name
属性。因此,谨慎的做法是进行一些检查,以防数据不符合您的预期。
一种方法是使用格式化程序将您的 dynamoDB 对象映射到您的预期响应:
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
是 client.get
的响应类型,但扫描或其他客户端方法可能具有不同的响应类型。但是,将客户端的响应映射到您需要的类型仍然遵循相同的原则。
您可以使用
class-transformer
。如果您需要不显示某些属性,您可以使用 @Exclude()
进行注释,然后使用 plainToClass
功能。
interface IPerson {
id: string,
name: string
@Exclude()
password: string
}
// for an object
const person = plainToClass(IPerson, response.Item)
// for an array
const people = response.Items.map((item) => plainToClass(IPerson, item));
或者更简单,使用
Object.assign
:
interface IPerson {
id: string,
name: string
}
// for an object
const person = Object.assign(IPerson, response.Item);
//for ann array
const people = response.Items.map((item) => Object.assign(IPerson), item);