DynamoDB の S3 インポート機能は CSV の場合だとすべて文字列型で取り込まれてしまう

2022.09.05

いわさです。

先日のアップデートで S3 バケットから直接 DynamoDB へデータをインポートすることが出来るようになりました。

待望の機能だったと思いますが、実は取り込みファイルが CSV の場合は一部を除いて文字列として取り込まれてしまうことを知ったので紹介します。

CSV でインポートする

早速ですが、まずは CSV でインポートしていみましょう。
S3 バケットへ以下のようなファイルを用意し、DynamoDB のインポート機能を使います。

hoge_str,hoge_num,hoge_bool
"iwasa1",100,true
"iwasa2",200,false
"iwasa3",300,true

インポートされたら、AWS CLI を使ってテーブルスキャンしてみます。

% aws dynamodb scan --table-name hoge0829csv
{
    "Items": [
        {
            "hoge_str": {
                "S": "iwasa3"
            },
            "hoge_num": {
                "S": "300"
            },
            "hoge_bool": {
                "S": "true"
            }
        },
        {
            "hoge_str": {
                "S": "iwasa2"
            },
            "hoge_num": {
                "S": "200"
            },
            "hoge_bool": {
                "S": "false"
            }
        },
        {
            "hoge_str": {
                "S": "iwasa1"
            },
            "hoge_num": {
                "S": "100"
            },
            "hoge_bool": {
                "S": "true"
            }
        }
    ],
    "Count": 3,
    "ScannedCount": 3,
    "ConsumedCapacity": null
}

DynamoDB JSON 形式で取得されるのですが、すべて "S" となっていますね。
これはデータ型を示していて文字列の S です。

どうやら、文字列として取り込まれてしまうこの挙動は回避出来ないようです。
以下へ制限事項として記述されています。

When importing from CSV files, all columns other than the hash range and keys of your base table and secondary indexes are imported as DynamoDB strings.

例外として、インポート時にパーティションキーやソートキーを指定することが出来て、その際にはデータ型を指定することが出来るので、キーに関してはデータ型を指定することが出来ます。
しかし、それ以外は取り込みファイル形式が CSV の場合は文字列になってしまいます。

なんてこったい。

DynamoDB JSON あるいは Ion に変換する

回避策としては、CSV 以外でサポートされている取り込み形式である DynamoDB JSON か Ion に変換するしかなさそうです。
汎用的な JSON がサポートされていればあるいは、という感じだったのですがやむを得ないですね。

ここでは以下2つの記事を参考に、CSV ファイルを DynamoDB JSON 形式の JSON ファイルへ変換してみます。

注意点があって、AWS CLI でスキャンした時の Items に全て配列で突っ込めばいいというわけではないです。
配列ではなく、複数の Item 項目で構成してやる必要があります。

ここでは以下のように読み込んだファイルごとにデータ型を指定しなおして Item に DynamoDB JSON 型でマーシャリングしたものを入れています。
JavaScript に慣れていないもので、「おいおいその実装はないだろう」みたいなところがあればご指摘ください。

const { marshall } = require("@aws-sdk/util-dynamodb");

const csvFilePath = 'input.csv'
const jsonFilePath = 'output.json'

const csv = require('csvtojson')
const fs = require('fs')

csv()
.fromFile(csvFilePath)
.then((rows)=>{
    rows.map((row) => {
        row.hoge_str = new String(row.hoge_str)
        row.hoge_num = new Number(row.hoge_num)
        row.hoge_bool = new Boolean(row.hoge_bool)
        var ddb_json = {}
        ddb_json["Item"] = marshall(row)
        fs.appendFile(jsonFilePath, JSON.stringify(ddb_json, null, 2) + "\n", (err) => {
            if(err){
                throw err;
            }
        })
    })
    console.log("JSON generated.");
})

入力ファイルから出力ファイルを生成してみましょう。

% cat input.csv 
hoge_str,hoge_num,hoge_bool
"iwasa1",100,true
"iwasa2",200,false
"iwasa3",300,true
                                                                                                         
% node hoge.js   
JSON generated.

% cat output.json                                                 
{
  "Item": {
    "hoge_str": {
      "S": "iwasa2"
    },
    "hoge_num": {
      "N": "200"
    },
    "hoge_bool": {
      "BOOL": true
    }
  }
}
{
  "Item": {
    "hoge_str": {
      "S": "iwasa1"
    },
    "hoge_num": {
      "N": "100"
    },
    "hoge_bool": {
      "BOOL": true
    }
  }
}
{
  "Item": {
    "hoge_str": {
      "S": "iwasa3"
    },
    "hoge_num": {
      "N": "300"
    },
    "hoge_bool": {
      "BOOL": true
    }
  }
}

それっぽいデータが生成されました。
このファイルを S3 へアップロードして DynamoDB から、DynamoDB JSON をインポートファイルフォーマットに指定してインポートします。

テーブルが作成されたらスキャンしてみましょう。

% aws dynamodb scan --table-name hoge0905dynamojson
{
    "Items": [
        {
            "hoge_str": {
                "S": "iwasa3"
            },
            "hoge_num": {
                "N": "300"
            },
            "hoge_bool": {
                "BOOL": true
            }
        },
        {
            "hoge_str": {
                "S": "iwasa2"
            },
            "hoge_num": {
                "N": "200"
            },
            "hoge_bool": {
                "BOOL": true
            }
        },
        {
            "hoge_str": {
                "S": "iwasa1"
            },
            "hoge_num": {
                "N": "100"
            },
            "hoge_bool": {
                "BOOL": true
            }
        }
    ],
    "Count": 3,
    "ScannedCount": 3,
    "ConsumedCapacity": null
}

今度はデータ型が保持されていますね。

さいごに

本日は DynamoDB の S3 インポート機能での CSV 取り込みで文字列になってしまう制限事項を紹介しました。
また、CSV を DynamoDB JSON へ変換しデータ型を保った状態で取り込めるかもためしてみました。

本当は Glue Job あたりで華麗にスパッと決めたかったのですが、DynamicFrame が難しすぎて JavaScript に逃げてしまいました。
DynamoDB JSON も Ion も AWS 独自のフォーマットなので、多くのユースケースでは CSV へ出力したものを取り込みしたくなりそうです。
Glue Job で変換出来るようにしておくと需要がそこそこありそうなので、一息ついたら頑張ってみたいと思っています。