LoginSignup
0
2

More than 3 years have passed since last update.

MongoDB: jsonの数値データをIntやDecimal128でインポートする

Posted at

MongoDBで、データをjsonからインポートしたい場合、mongoimportを使用するのが一般的だと思います。

ですが、jsonでは数値のTypeは区別されないため、mongoimportをそのまま使用すると、全てがfloating point numberと扱われてしまうようです。

Schema Validationを定義しており、double以外の数値型を設定している場合、これでは弾かれてしまうので、なんとかする必要があります。

しばらく詰まって辛かったのでメモ書きしておきます。

やり方は色々あるっちゃある

jsonとmongoimportの組み合わせでは、どう頑張っても無理そうなので、回避策を探します。

  1. CSVにする
  2. jsでやる
  3. Schema Validationを無効化して入れて、その後でここのデータを正しいTypeに更新する
  4. BSONでやる <= これを選択

CSVに変換してから

最初、jsonとmongoimportでなんとかできないかと思い、ドキュメントを読んでいたところ見つけたのが、columnsHaveTypesオプションです。

これはどうやら、フィールド毎にTypeを指定できるもののようですが、残念ながらCSVとTSVからのインポートにのみ対応しているオプションのようで、ご丁寧に「json噛ませるとエラーになるからな」と注意書きがなされています。

まぁなら、何かしらの方法でjsonをCSVに変換してからインポートすればいけそうです。(未検証)

jsでスクリプト書く

Mongo Shellはjsを呼べるので、スクリプトを書くのも手です。

ですが、どうやらMongoShellでjsonファイルを直接読み込むのは無理で、jsファイルに変換する必要があるようです。

ref: https://stackoverflow.com/questions/27670996/import-json-with-mongo-script

ちょっと流石にめんどいし何より気持ち悪いのでやめました。

まぁMongoShellを使わず、どっか適当なところにnpmの環境を作って、MongoDBのNode.js Driverとかを使ってやれば、そんなことせずともいけそうです。(未検証、けどこれが一番順当な方法かも)

Schema Validationを無効化

とりあえず入れてしまって、後からTypeを変える方法も考えました。

後から、Bypass Document Validationする方法は色々あるようで、findAndModifyとかが使えれば、サクサク終わりそうと期待しました。

ですが、このfindAndModifyだと、ヒットしたデータのCurrent Valueを、修正時に参照することができなさそうです。

ref: https://stackoverflow.com/questions/3788256/mongodb-updating-documents-using-data-from-the-same-document

つまり、やるなら1つ1つのデータを取得して、そのデータを元に新しいオブジェクトを$setするみたいなスクリプを書く必要があります。

くそ

BSONにしてしまえば良い

よくよく考えれば、jsonだと表現できる幅が限られているからimport時にType情報がなくなるわけで、最初からbsonでインポートすれば問題は解決です。

"bson"というnpmライブラリが公式から出てるので、それを使ってjsonをbsonに変換してから、インポートさせれば良いです。

MongoDBにbsonで直接インポートするには、mongoimportではなくmongorestoreを使います。

jsonからbsonへの変換

npm add bsonしたら、変換するスクリプトを書きます。もちろんtsで書くので、npm add @types/bsonも忘れずにします。

import fs from 'fs';
import { promisify } from 'util';
import BSON from 'bson';

const Int32 = BSON.Int32;
const Double = BSON.Double;

type NewData = {
  code: BSON.Int32;
  price: BSON.Double;
}

const newData: NewData = {
  code: new Int32(9064),
  price: new Double(2500.05)
}

const newData2: NewData = {
  code: new Int32(9984),
  price: new Double(5200)
}

const save = async () => {
  await promisify(fs.writeFile)('path/to/file.bson', BSON.serialize(newData));
  await promisify(fs.appendFile)('path/to/file.bson', BSON.serialize(newData2));
}

save();

中身はこんな感じです。(あくまで参考例レベルですので悪しからず)
ファイルに書き込むときにBSON.serialize()することで、bsonとして正しく保存できます。

複数データを入れる場合の注意:Arrayにしない

const save = async () => {
  await promisify(fs.writeFile)('path/to/file.bson', BSON.serialize([newData, newData2]));
}

最初、このような感じで配列をBSONに書き込んでいました。

しかしその場合、インポートしようとすると1つの配列が1つのデータとして登録されてしまいます。

mongoimportの場合は--jsonArrayオプションがあったので、mongorestoreにも--bsonArray的なオプションがあるのかと思ったのですが、ないです。

この解決方法を探して小一時間さまよっていましたが、結論は「そもそもArrayにする必要がない」です。

ref: https://stackoverflow.com/questions/56585231/how-to-deserialize-dumped-bson-with-arbitrarily-many-documents-in-javascript

Which clearly breaks the JSON syntax. The internal BSON is of similar shape, but it seems BSON allows this kind of multi-object stuffing in one file.

これを知るために結構時間を無駄にした。。。。😭

Arrayにせず、直接appendFileで追記していきましょう。

保存はmongoimportではなくmongorestoreで

dumpしたデータの実態はただのbsonファイルなので、mongorestoremongodumpで出力したデータでなくとも、.bsonであれば使用できます。

restore.sh
DB_NAME=$1
COLLECTION_NAME=$2
FILE_PATH=$3

// authはあくまで例
mongorestore \
  --authenticationDatabase admin -u root -p root \ 
  -d $DB_NAME -c $COLLECTION_NAME $FILE_PATH

疲れた 😇

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2