初級読了 3 分

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 をそのまま格納するわけにいきません — 各値の正確な型を 明示する必要があります。型記述子が、あらゆるリクエストとレスポンスで、それをロスレスに 行う方法です。

エンコードの仕組み

すべての属性値は、キーが 型記述子 である単一キーのオブジェクトになります。

記述子
SString{"S": "open"}
NNumber(文字列として){"N": "3"}
BBinary{"B": "dGV4dA=="}
BOOLBoolean{"BOOL": true}
NULLNull{"NULL": true}
LList{"L": [{"S": "a"}, {"N": "1"}]}
MMap{"M": {"k": {"S": "v"}}}
SS / NS / BSString / 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 は重複を排除し順序を持たないからです。tagsSS であるべきか L であるべきかは、コンバータが代わりに決められないモデリングの判断であり、まさにこれが エンコードを理解することが重要な理由です。

DynoTable で変換する

これを手で読み書きする必要はめったにありません。素の JSON を DynamoDB JSON コンバータ に貼り付けてマーシャル (および逆変換)し、リクエストを組み立てるときは DynamoDB Expression Builder が、式と並んで正しく マーシャルされた属性値マップを出力します。アプリ本体では、DynoTable がアイテムを素の、 読みやすい値として表示し、書き込み時に代わりにマーシャルします。

DynoTable がアイテムを素の値として表示し、生の DynamoDB JSON も利用できる。
DynoTable がアイテムを素の値として表示し、生の DynamoDB JSON も利用できる。

落とし穴と次のステップ

  • 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 にはドキュメントクライアントを既定に すべきです。低レベルのマップに手を伸ばすのは、 更新式 を手で書くときや、ライブラリが型付きの 属性値を期待するときです。

式の属性値もマーシャルされる

ConditionExpressionUpdateExpressionFilterExpression のプレースホルダ (: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 に戻しましょう。エクスポート操作は、チケットやテストケース用にどちらの表現でも コピーできます。

更新日