AWS SDK for JavaScript v3でDynamoDB JSONと通常のJSONの間でデータ構造の変換を行う ~ util-dynamodb ~

2022.05.09

いわさです。

DynamoDBから取得したデータですが、普通のJSONと少し違いますよね。
AWS SDK for JavaScriptのDynamoDBClientを使ってDynamoDBをスキャンすると、データ構造が想定と違ったのでちょっと悩みました。

AWS SDK for JavaScript v3では標準的なJSON形式とDynamo JSON形式の間で相互にデータ構造を変換する機能が提供されているので使ってみました。

データ型記述子が追加されている

こちらのような項目が登録されている場合、一般的なJSONのイメージだと以下のような形式を期待します。

[
    {
        "id": "2",
        "str1": "hoge2"
    },
    {
        "id": "1",
        "str1": "hoge1"
    }
]

しかし、AWS CLIやSDKでスキャンをすると以下のような形式になります。

[
    {
        "id": {
            "S": "2"
        },
        "str1": {
            "S": "hoge2"
        }
    },
    {
        "id": {
            "S": "1"
        },
        "str1": {
            "S": "hoge1"
        }
    }
]

JSON形式ではあるのですが、データ型を示す構造と共に値が格納されています。
これは、DynamoDBのリクエストとレスポンスで使用されるデータ型記述子と呼ばれるものです。

CLIのエクスポート機能でもDYNAMODB_JSONというフォーマット名になっています。

JavaScriptでスキャン

そのままスキャンする時点では、JavaScriptで以下のような実装を行っていました。

$ npm init
$ npm install @aws-sdk/client-dynamodb

hoge.js

const { DynamoDBClient, ScanCommand } = require("@aws-sdk/client-dynamodb");

const client = new DynamoDBClient({
    region: "ap-northeast-1"
});
const command = new ScanCommand({
    TableName: "hoge"
});

const run = async() => {
    const response = await client.send(command);
    response.Items.forEach(function(item){
        console.log(item)
    })
}
run();
$ node hoge.js
{ id: { S: '2' }, str1: { S: 'hoge2' } }
{ id: { S: '1' }, str1: { S: 'hoge1' } }

SDKを使っても、データ型記述子が設定されていますね。

util-dynamodbモジュールが提供されている

@aws-sdk/util-dynamodbで相互変換を行うためのユーティリティが提供されています。

unmarshall

モジュールを追加して試してみましょう。

$ npm install @aws-sdk/util-dynamodb  
npm WARN 0509dynamo@1.0.0 No description
npm WARN 0509dynamo@1.0.0 No repository field.

+ @aws-sdk/util-dynamodb@3.85.0
added 1 package from 1 contributor and audited 74 packages in 3.201s

2 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

hoge.js

const { DynamoDBClient, ScanCommand } = require("@aws-sdk/client-dynamodb");
const { unmarshall } = require("@aws-sdk/util-dynamodb");

const client = new DynamoDBClient({
    region: "ap-northeast-1"
});
const command = new ScanCommand({
    TableName: "hoge"
});

const run = async() => {
    const response = await client.send(command);
    response.Items.forEach(function(item){
        console.log(unmarshall(item))
    })
}
run();
$ node hoge.js
{ id: '2', str1: 'hoge2' }
{ id: '1', str1: 'hoge1' }

おお、取得することが出来ました!

marshall

逆の変換も行ってみましょう。

hoge.js

const { DynamoDBClient, ScanCommand } = require("@aws-sdk/client-dynamodb");
const { unmarshall, marshall } = require("@aws-sdk/util-dynamodb");

const client = new DynamoDBClient({
    region: "ap-northeast-1"
});
const command = new ScanCommand({
    TableName: "hoge"
});

const run = async() => {
    const response = await client.send(command);
    response.Items.forEach(function(item){
        console.log(item)
        tmpItem = unmarshall(item)
        console.log(tmpItem)
        console.log(marshall(tmpItem))
    })
}
run();
$ node hoge.js
{ id: { S: '2' }, str1: { S: 'hoge2' } }
{ id: '2', str1: 'hoge2' }
{ id: { S: '2' }, str1: { S: 'hoge2' } }
{ id: { S: '1' }, str1: { S: 'hoge1' } }
{ id: '1', str1: 'hoge1' }
{ id: { S: '1' }, str1: { S: 'hoge1' } }

こちらも、通常のJSON構造に一度戻した後に、再度DynamoDB JSON構造に変換出来ました。

さいごに

以前は、AWS.DynamoDB.Converterを使って実現していたようです。
今はAWS SDK for JavaScript v3から提供されているものが推奨されているとのこと。

ちなみに、DynamoDB JSONと通常のJSON間での互換性についてはこの記事では言及していませんのでご注意ください。
マネジメントコンソール上でも表示形式を変換出来ますが、一部データはレンダリングが出来ないと説明されています。