DynamoDB JSON 與 Marshalling
你頭一次從 DynamoDB API 讀到原始資料時,它看起來不像你放進去的那個 JSON。一個普通物件 {"status": "open", "priority": 3} 回來時成了 {"status": {"S": "open"}, "priority": {"N": "3"}}。每個值都被裹進一個單鍵物件裡,標明它的型別。那層包裹就是 DynamoDB JSON,而在它和普通 JSON 之間來回轉換,就叫 marshalling。
它不是噪聲——它是 DynamoDB 在傳輸線上保持型別無歧義的方式。但它會絆倒任何期待普通 JSON 的人,而且手寫它很容易出錯。
什麼是 DynamoDB JSON?
DynamoDB JSON 是 DynamoDB 使用的、帶型別標籤的傳輸格式,其中每個值都被裹進一個標明其型別的單鍵物件裡——字串是 {"S": "open"},數字是 {"N": "3"}。把普通 JSON 轉成它(以及轉回來)就叫 marshalling。它讓型別無歧義,因為普通 JSON 表達不了集合或二進位制,而且由於 DynamoDB 的數字在傳輸線上以字串形式傳送,一個不帶標籤的 3 會有歧義。
- DynamoDB JSON 給每個值都標上它的型別——字串是
{"S": "..."},數字是{"N": "..."},依此類推。 - Marshalling = 普通 JSON → DynamoDB JSON。Unmarshalling = 反過來。
- 數字在傳輸線上是字串——是
{"N": "3"},不是{"N": 3}——為的是保留精度。 - 這些型別標籤就是那套資料型別系統,你早已用它建模:S、N、B、BOOL、NULL、L、M、SS、NS、BS。
- 別手寫它。SDK 的文件用戶端(或一個轉換器)替你 marshal;只在除錯或構建運算式時才手工做。
問題所在:普通 JSON 不夠用
JSON 恰好只有三種標量型別——字串、數字、布林——外加 null、陣列和物件。DynamoDB 有更多:二進位制,以及三種 集合 型別(字串集合、數字集合、二進位制集合)——這些 JSON 壓根表達不了。而且因為 DynamoDB 的數字在傳輸線上以字串形式傳送,一個不帶標籤的 3 會有歧義——再加上 JSON 分不清列表和集合。
所以 DynamoDB 沒法直接原樣存你的 JSON——它需要每個值的確切型別被明確寫出。型別描述符就是它做到這一點的方式,無損地,在每一次請求和響應上。
編碼如何工作
每個屬性值都變成一個單鍵物件,它的鍵是一個型別描述符:
| 描述符 | 型別 | 示例 |
|---|---|---|
S | 字串 | {"S": "open"} |
N | 數字(作為字串) | {"N": "3"} |
B | 二進位制 | {"B": "dGV4dA=="} |
BOOL | 布林 | {"BOOL": true} |
NULL | 空值 | {"NULL": true} |
L | 列表 | {"L": [{"S": "a"}, {"N": "1"}]} |
M | 對映 | {"M": {"k": {"S": "v"}}} |
SS / NS / BS | 字串 / 數字 / 二進位制集合 | {"SS": ["a", "b"]} |
列表和對映把同樣的描述符一路巢狀到底,所以一個深層結構化的項就變成深層被包裹的。數字在傳輸線上是字串是有意為之——這讓 DynamoDB 能保留它完整的 38 位數字精度,而一個 JSON 數字(一個 IEEE-754 雙精度,約 15–17 位有效數字)會悄悄把它舍入掉。這些就是你用來建模的那套資料型別;DynamoDB JSON 只是它們在傳輸線上明確的形式,定義於 AWS 底層 API 參考。
例項:一條審計日誌條目
你會在應用裡寫的普通 JSON:
{
"actor": "u-204",
"action": "ticket.close",
"ticketId": 8842,
"tags": ["billing", "urgent"],
"redacted": false
}為 API marshal 成 DynamoDB JSON:
{
"actor": {"S": "u-204"},
"action": {"S": "ticket.close"},
"ticketId": {"N": "8842"},
"tags": {"SS": ["billing", "urgent"]},
"redacted": {"BOOL": false}
}注意這個項背後的選擇:ticketId 變成了帶字串值的 N;tags 作為一個字串集合(SS)而非列表,是一個手工的建模選擇——一個吃普通 JSON 的通用轉換器會發出 L,因為 JSON 陣列是有序且可重複的,而 SS 會去重且無序。tags 到底該是 SS 還是 L,是一個轉換器替你做不了的建模決定,而這恰恰是為什麼理解這套編碼很重要。
在 DynoTable 裡轉換
你很少需要手工讀寫這個。把普通 JSON 粘進 DynamoDB JSON 轉換器 來 marshal 它(以及轉回來),而當你在組裝一個請求時,DynamoDB 運算式構建器 會在運算式旁邊發出正確 marshal 後的屬性值對映。在應用本身裡,DynoTable 把項顯示為普通、可讀的值,並在寫入時替你 marshal 它們。

陷阱與後續步驟
- DynamoDB JSON 裡數字是字串——
{"N": "3"}。引號很要緊;別發出一個光禿禿的數字。 - 集合與列表之分是一個建模決定,這套編碼把它顯式化了——慎重選擇(見資料型別)。
- 在應用程式碼裡優先用 SDK 文件用戶端,而不是手工 marshal;把手寫 DynamoDB JSON 留給除錯和運算式。
- 空字串對於非鍵屬性是允許的(自 2020 年起),但對錶鍵和索引鍵仍然被拒絕,而且歷來會絆倒工具鏈——把邊界情況驗證一下。
想把項當作普通值來瀏覽,而不是靠肉眼解碼型別標籤?下載 DynoTable,直接處理你的資料。
低階用戶端與文件用戶端
AWS SDK 提供兩層:
| 層 | 輸入形狀 | 誰是元帥? |
|---|---|---|
@aws-sdk/client-dynamodb(低階) | DynamoDBJSONAttributeValue地圖 | 您的程式碼或幫手 |
@aws-sdk/lib-dynamodb(文件) | 普通 JS 物件 | 傳送/接收 SDK |
應用程式程式碼應預設為 PutItem/GetItem 的文件用戶端。手動創作時獲取低階地圖
update expressions 或當圖書館期望時輸入的屬性值。
運算式屬性值也被編組
ConditionExpression、UpdateExpression 和 FilterExpression 預留位置
(:val, :inc) 對映到 ExpressionAttributeValues 中的編組值:
":status": {"S": "open"}
":count": {"N": "1"}不匹配 — 在低階用戶端上傳送 "open" 而沒有 S 包裝器 —
返回ValidationException。的
expression builder 旁邊發出地圖運算式字串,以便預留位置和型別保持對齊。衝突的屬性名稱
reserved words使用
ExpressionAttributeNames (#st) 代替;檢查器工具輸出別名地圖準備貼上。
解析測試中的驚喜
編組中常見的測試失敗:
- 空集 — DynamoDB 拒絕空
SS/NS/BS;省略屬性相反。 - 在
N中浮動 — 線上路上將"3.14"作為字串傳送,而不是 JSON 數字。 - Node 中的二進位制 — 文件用戶端中的
Uint8Array;原始 JSON 中的 base64。 - 未定義的屬性 — 文件用戶端條
undefined;低階用戶端可能會傳送無效的有效負載。當 Lambda 記錄原始 API 響應時,將一項貼上到 DynamoDB JSON converter 到可讀的普通 JSON 在與固定裝置進行比較之前。
標記的大小影響
每個型別包裝器都會新增位元組。一個扁平的JSON物件逐個欄位編組增長大約 30-40% 取決於屬性名稱——通貨膨脹所提供的
item size 和 RCU/WCU 舍入。大地圖使用短屬性名稱攤銷開銷;微小的布林標誌仍然值得他們的鍵名加上{"BOOL":true}。在批次載入編組項目之前,請檢查
item-size calculator 所以批次寫入不會意外超過 16 MB 請求限制。
DynoTable的兩種看法
項目編輯器每天都會保持不可見的編組——您編輯簡單的值,並在傳送時提交元帥。除錯複製自的生產項目時 CloudWatch 日誌,切換到 DynamoDB JSON 檢視以檢視確切的標籤,然後切換回來到 Plain JSON 進行編輯。匯出操作複製票證的任一表示形式和測試用例。


