Node.js(AWS SDK v3)中的 DynamoDB PutItem
PutItem 写入一整个项,并替换掉主键相同的既有项(基于项的操作讲了它和 UpdateItem 的区别)。v3 客户端直接发送 DynamoDB JSON,所以 Item 里装的是 { S: … } / { N: … } 这样的值,而不是普通的 JavaScript 值。
代码
import {DynamoDBClient, PutItemCommand} from '@aws-sdk/client-dynamodb';
const client = new DynamoDBClient({region: 'us-east-1'});
const command = new PutItemCommand({
TableName: 'Music',
Item: {
Artist: {S: 'Arturo Sandoval'},
SongTitle: {S: 'Cubano Chant'},
AlbumTitle: {S: 'Danzon'},
Year: {N: '1994'},
Awards: {N: '0'}
},
ConditionExpression: 'attribute_not_exists(#cond0) AND attribute_not_exists(#cond1)',
ExpressionAttributeNames: {
'#cond0': 'Artist',
'#cond1': 'SongTitle'
}
});
try {
await client.send(command);
console.log('Song written');
} catch (err) {
if (err.name === 'ConditionalCheckFailedException') {
console.log('A song with that key already exists — not overwritten');
} else {
throw err;
}
}说明
err.name 是正确的判断依据,而它不是错误对象上唯一的东西。把上面那次条件失败捕获下来并打印出这个对象,得到的是:
err.name ConditionalCheckFailedException
err instanceof Error true
err.message The conditional request failed
err.$metadata.httpStatusCode 400每个 v3 错误都带着 $metadata,里面有状态码、请求 id 和尝试次数,这正是你想写进日志的东西。err.name 在各个模块化包之间都是稳定的;instanceof ConditionalCheckFailedException 也能用,但那会把类作为值引入,于是打包器会把它留下。
错误可以把挡住这次写入的那个项交给你。给命令加上 ReturnValuesOnConditionCheckFailure: 'ALL_OLD',err.Item 就会带着内容回来:上面那次运行里是五个属性,其中 Year 是 {"N":"1994"}。大多数"只创建"的处理逻辑会在失败之后再做一次 GetItem 去看看那里原本是什么。那一次往返是可以省掉的。(ReturnValues: 'ALL_OLD' 是它在成功路径上的表亲;ReturnValues 讲了其余的部分。)
marshall() 拒绝的输入比你以为的多。把本页这种带类型的 Item 换成 DynamoDBDocumentClient 加普通对象,是通常的下一步,而 @aws-sdk/util-dynamodb 默认就很严格。三个真实的抛错,原样照录:
{Genre: undefined} Pass options.removeUndefinedValues=true to remove undefined values from map/array/set.
{tags: new Set()} Pass a non-empty set, or options.convertEmptyValues=true.
{n: 9007199254740993} Number 9007199254740992 is greater than Number.MAX_SAFE_INTEGER. Use NumberValue from @aws-sdk/lib-dynamodb.第一个才是会进到生产环境的那个:一个可选字段是 undefined 而不是干脆不存在,就会在编组时抛错,而在 DynamoDBDocumentClient.from(client, {marshallOptions}) 里设 removeUndefinedValues: true 是标准修法。
再读一遍第三行。字面量是 9007199254740993,消息里引用的却是 9007199254740992。JavaScript 在 SDK 看到这个值之前就已经把它四舍五入了,所以 SDK 报的是它收到的东西。这正是 DynamoDB 用字符串传输 N 的全部理由:它能保 38 位精度,而一个 JS number 只有 15 到 17 位。真正属于标识符的东西应该放在 S 里,真正属于小数的东西应该放进 NumberValue,或者你自己格式化的字符串。
ConditionExpression 即使被拒绝也要花写容量。AWS 的原话是:"if the expression evaluates to false, DynamoDB still consumes write capacity units from the table"(2026-07-28 获取)。一个紧凑的"只创建"重试循环是按尝试次数计费的。作为参照,成功写入一个约 15 KB 的项报告的是 "CapacityUnits": 15;写入按每 1 KB 向上取整,而不是读取用的 4 KB。
这些别名是承重的。#cond0/#cond1 通过 ExpressionAttributeNames 解析成 Artist/SongTitle。直接内联名字一直能用,直到其中一个撞上保留字,然后表达式就会在一个你根本没碰过的属性上失败。
用可视化的方式来做
上面那些编组规则,最容易的检查方式是把两种形式并排看一遍。免费的 DynamoDB JSON 转换器能把普通 JSON 变成带类型的 { S: … } 形式再变回来,于是你可以在发送之前确认 marshall() 会产出什么。
要在你自己的表上写入和编辑项——每个属性一个表单、有类型选择器、还能把结果作为 SDK v3 代码复制出来——请下载 DynoTable。
相关指南
- DynamoDB 条件表达式——
attribute_not_exists、乐观锁,以及更多。 - DynamoDB 数据类型——每种属性类型是怎么写的。
- DynamoDB ConditionalCheckFailedException——项已存在时,"只创建"条件抛出的是什么。
- DynamoDB ValidationException——项或表达式格式有问题时的兜底错误。
参考资料
- PutItem — Amazon DynamoDB API Reference
- PutItemCommand — AWS SDK for JavaScript v3 Reference
- Capacity unit consumption — Amazon DynamoDB Developer Guide
- Condition expressions — Amazon DynamoDB Developer Guide
- Working with items and attributes — Amazon DynamoDB Developer Guide
2026-07-28 在 Node v24.18.0 上、使用 @aws-sdk/client-dynamodb 3.1095.0 与 @aws-sdk/util-dynamodb 3.996.7,针对 9000 端口上的 DynamoDB Local(amazon/dynamodb-local)复现。上面的错误字符串、对象结构和容量读数都是捕获到的输出,原样照录。