入門閱讀時間 2 分鐘

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 與 DynamoDB JSON 之間來回轉換稱為 marshalling。這套格式讓型別不再模糊,因為普通 JSON 無法表達集合、二進位,也分不出字串 "3" 與數字 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 的 document client(或一個轉換器)會替你 marshal;只在除錯或建構運算式時才手動做。

問題:普通 JSON 不夠用

JSON 恰好只有三種純量 — 字串、數字、布林 — 外加 null、陣列與物件。DynamoDB 有更多:二進位,以及三種 set 型別(字串集合、數字集合、二進位集合),那是 JSON 根本無法表達的。JSON 也分不出字串 "3" 與數字 3,或一個串列與一個集合。

所以 DynamoDB 不能就這樣原封不動地存你的 JSON — 它需要每個值的確切型別被明確陳述。型別描述符就是它做這件事的方式,在每個請求與回應上都無損地進行。

編碼如何運作

每個屬性值都變成一個單鍵物件,其鍵是一個型別描述符

描述符型別範例
S字串{"S": "open"}
N數字(以字串表示){"N": "3"}
B二進位{"B": "dGV4dA=="}
BOOL布林{"BOOL": true}
NULLNull{"NULL": true}
L串列{"L": [{"S": "a"}, {"N": "1"}]}
MMap{"M": {"k": {"S": "v"}}}
SS / NS / BS字串/數字/二進位集合{"SS": ["a", "b"]}

串列與 map 把相同的描述符一路向下巢狀,所以一個結構很深的項目就變成包裝很深。數字刻意以字串在傳輸上行進 — 這讓 DynamoDB 能保留任意精度,而一個 JSON 數字(一個 IEEE-754 double)會悄悄把它捨入。這些就是你拿來建模的同一批資料型別;DynamoDB JSON 只是它們明確的傳輸形式,定義在 AWS 低階 API 參考裡。

實作範例:一筆稽核記錄條目

你會在 app 裡寫的普通 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}
}

注意 marshaller 做的選擇:ticketId 變成了 N,值是字串tags 變成了一個字串集合SS),而不是一個串列 — 一個刻意的決定,因為集合會去重且無序。tags 該是 SS 還是 L,是一個轉換器無法替你做的建模抉擇,這正是為什麼理解這套編碼很重要。

在 DynoTable 中轉換

你很少需要手動讀寫這個。把普通 JSON 貼進 DynamoDB JSON 轉換器來 marshal 它(也能轉回去),而當你在組裝一個請求時,DynamoDB 運算式建構器會在運算式旁,連同正確 marshal 好的屬性值對照一起發出。在 app 本身裡,DynoTable 把項目顯示為普通、可讀的值,並在寫入時替你 marshal。

DynoTable 把一個項目顯示為普通值,同時提供原始的 DynamoDB JSON。
DynoTable 把一個項目顯示為普通值,同時提供原始的 DynamoDB JSON。

陷阱與下一步

  • 數字在 DynamoDB JSON 裡是字串{"N": "3"}。引號很重要;別發出一個裸數字。
  • 集合與串列之爭是一個建模決定,而這套編碼把它攤在眼前 — 慎重地挑(請見資料型別)。
  • 在 app 程式碼裡,偏好 SDK 的 document client 而非手動 marshalling;把手寫 DynamoDB JSON 留給除錯與運算式。
  • 空字串是被允許的,在項目裡,但歷來會絆倒一些工具 — 驗證邊界情況。

想把項目當成普通值瀏覽,而不是用眼睛解碼型別標記嗎?下載 DynoTable,直接處理你的資料。

已更新