DynamoDB JSON とマーシャリング
DynamoDB API から生のデータを初めて読むと、それは自分が入れた JSON のようには見えません。
{"status": "open", "priority": 3} のような素のオブジェクトが、
{"status": {"S": "open"}, "priority": {"N": "3"}} として返ってきます。すべての値が、
その型を名指しする1キーのオブジェクトに包まれています。その包み込みが DynamoDB JSON
であり、それとの相互変換が マーシャリング と呼ばれます。
これはノイズではなく — DynamoDB がワイヤ上で型を曖昧にしないための仕組みです。しかし、 素の JSON を期待する人をつまずかせ、手書きするとエラーが起きやすいものです。
DynamoDB JSON とは?
DynamoDB JSON は、DynamoDB が使う型タグ付きのワイヤ形式で、すべての値がその型を名指しする
1キーのオブジェクトに包まれます — 文字列なら {"S": "open"}、数値なら {"N": "3"} です。
素の JSON をそれに変換すること(および逆変換)がマーシャリングと呼ばれます。これは型を
曖昧にしないためのもので、素の JSON はセットやバイナリを表現できず、また DynamoDB の数値は
文字列としてワイヤに載るため、タグのない 3 は曖昧になるからです。
- DynamoDB JSON はすべての値にその型のタグを付けます — 文字列なら
{"S": "..."}、 数値なら{"N": "..."}、といった具合です。 - マーシャリング = 素の JSON → DynamoDB JSON。アンマーシャリング = その逆。
- 数値はワイヤ上では文字列です —
{"N": "3"}であって{"N": 3}ではありません — 精度を保つためです。 - 型タグは、あなたがすでにモデル化に使っているデータ型システムです — S、N、B、BOOL、 NULL、L、M、SS、NS、BS。
- 手書きしないこと。 SDK のドキュメントクライアント(またはコンバータ)が代わりに マーシャルします。手作業でやるのは、デバッグや式の組み立てのときだけにしましょう。
問題: 素の JSON では足りない
JSON にはスカラーがちょうど3種類 — 文字列、数値、ブール値 — に加えて null、配列、
オブジェクトがあります。DynamoDB にはもっとあります。バイナリと、JSON がまったく表現
できない3つの セット 型(文字列セット、数値セット、バイナリセット)です。そして
DynamoDB の数値は文字列としてワイヤに載るため、タグのない 3 は曖昧になります — さらに
JSON はリストとセットを区別できません。
だから DynamoDB はあなたの JSON をそのまま格納するわけにいきません — 各値の正確な型を 明示する必要があります。型記述子が、あらゆるリクエストとレスポンスで、それをロスレスに 行う方法です。
エンコードの仕組み
すべての属性値は、キーが 型記述子 である単一キーのオブジェクトになります。
| 記述子 | 型 | 例 |
|---|---|---|
S | String | {"S": "open"} |
N | Number(文字列として) | {"N": "3"} |
B | Binary | {"B": "dGV4dA=="} |
BOOL | Boolean | {"BOOL": true} |
NULL | Null | {"NULL": true} |
L | List | {"L": [{"S": "a"}, {"N": "1"}]} |
M | Map | {"M": {"k": {"S": "v"}}} |
SS / NS / BS | String / Number / Binary のセット | {"SS": ["a", "b"]} |
リストとマップは、同じ記述子を末端までネストするので、深く構造化されたアイテムは深く 包まれます。数値がワイヤ上で 文字列 として載るのは意図的です — JSON の数値 (IEEE-754 の倍精度、有効数字は約15〜17桁)ならこっそり丸めてしまう、DynamoDB の完全な 38桁の数値精度を保てるからです。これらは、あなたがモデル化に使うのと同じ データ型 です。DynamoDB JSON は単にその明示的なワイヤ上の形であり、 AWS 低レベル API リファレンス で定義されています。
具体例: 監査ログのエントリ
アプリで書くであろう素の JSON:
{
"actor": "u-204",
"action": "ticket.close",
"ticketId": 8842,
"tags": ["billing", "urgent"],
"redacted": false
}API 用に 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 コンバータ に貼り付けてマーシャル (および逆変換)し、リクエストを組み立てるときは DynamoDB Expression Builder が、式と並んで正しく マーシャルされた属性値マップを出力します。アプリ本体では、DynoTable がアイテムを素の、 読みやすい値として表示し、書き込み時に代わりにマーシャルします。

落とし穴と次のステップ
- DynamoDB JSON では数値は文字列です —
{"N": "3"}。クォートが重要です。裸の数値を 出力しないこと。 - セットかリストかはモデリングの判断 で、エンコードがそれを可視化します — 意図をもって 選びましょう(データ型 を参照)。
- アプリのコードでは手作業のマーシャリングより SDK のドキュメントクライアントを — 手作業の DynamoDB JSON は、デバッグと式のためにとっておきましょう。
- 空文字列は非キー属性に許可されています(2020年以降)が、テーブルとインデックスの キーには依然として拒否され、歴史的にツールをつまずかせてきました — エッジケースを 検証しましょう。
型タグを目でデコードする代わりに、アイテムを素の値として閲覧したいですか? DynoTable をダウンロード して、データを直接扱いましょう。
低レベルクライアントとドキュメントクライアント
AWS SDK は2つの層を提供します。
| 層 | 入力の形 | 誰がマーシャルするか |
|---|---|---|
@aws-sdk/client-dynamodb(低レベル) | DynamoDB JSON の AttributeValue マップ | 自分のコードかヘルパー |
@aws-sdk/lib-dynamodb(ドキュメント) | 素の JS オブジェクト | 送受信時に SDK |
アプリケーションコードは、PutItem/GetItem にはドキュメントクライアントを既定に
すべきです。低レベルのマップに手を伸ばすのは、
更新式 を手で書くときや、ライブラリが型付きの
属性値を期待するときです。
式の属性値もマーシャルされる
ConditionExpression、UpdateExpression、FilterExpression のプレースホルダ
(:val、:inc)は、ExpressionAttributeValues の中のマーシャル済みの値に対応します。
":status": {"S": "open"}
":count": {"N": "1"}食い違い — 低レベルクライアントで S のラッパーなしに "open" を送る — は
ValidationException を返します。式ビルダー は
式の文字列と並んでこのマップを出力するので、プレースホルダと型がずれません。
予約語 と衝突する属性 名 には、代わりに
ExpressionAttributeNames(#st)を使います。チェッカーツールは貼り付けられる形の
エイリアスマップを出力します。
テストでのアンマーシャルの落とし穴
マーシャリング由来のよくあるテスト失敗:
- 空のセット — DynamoDB は空の
SS/NS/BSを拒否します。代わりに属性を省きま しょう。 Nの中の浮動小数点 — ワイヤ上では JSON の数値ではなく文字列として"3.14"を 送ります。- Node でのバイナリ — ドキュメントクライアントでは
Uint8Array、生の JSON では base64 です。 - undefined の属性 — ドキュメントクライアントは
undefinedを取り除きますが、低 レベルクライアントは不正なペイロードを送ることがあります。
Lambda が生の API レスポンスをログに出しているときは、フィクスチャと差分を取る前に アイテムを1つ DynamoDB JSON コンバータ に貼り付けて、 読みやすい素の JSON に戻しましょう。
タグ付けがサイズに与える影響
型のラッパーはすべてバイトを増やします。フラットな JSON オブジェクトをフィールドごとに
マーシャルすると、属性名にもよりますがワイヤ上でおよそ30〜40%膨らみます — その膨張は
アイテムサイズ と RCU/WCU の切り上げに効いてきます。
属性名の短い大きなマップはオーバーヘッドを薄められますが、小さなブール値のフラグでも
キー名と {"BOOL":true} の分は払うことになります。
マーシャル済みのアイテムを一括ロードする前に、バッチ書き込みが16 MB のリクエスト上限を 思いがけず超えないよう、アイテムサイズ計算機 で 合計バイト数を確認しましょう。
DynoTable の2つのビュー
アイテムエディタは日常的にはマーシャリングを見えないままにします — 素の値を編集し、 コミット時に送信でマーシャルされます。CloudWatch のログからコピーした本番のアイテムを デバッグするときは、DynamoDB JSON ビューに切り替えて正確なタグを確認し、編集のときは 素の JSON に戻しましょう。エクスポート操作は、チケットやテストケース用にどちらの表現でも コピーできます。


