【Node.js】JSON文字列をCSVファイルとして吐き出す


要件

・DBに格納されているデータをCSVでダウンロードできるようにする
・データはユーザーが回答したアンケートの結果

使用したモジュール

・json2csv
https://www.npmjs.com/package/json2csv

開発

1. データの成形

今回は、ユーザーが回答したアンケート結果をCSVとして出力したいと思いますので、表示する項目をユーザー名メールアドレスアンケート設問回答結果とします。

データを成形する
    // DBから出力したいデータを取得する
    const data = await getData.getUserData(user_id);

    // データをJSON型に整える 
    const groupData = data.reduce((result, current) => {
      // user_idごとにデータをまとめる
      const element = result.find((p) => {
        return p.id === current.user_id;
      });
      // user_idが既にあればquestionとanswerのみを追加する
      if (element) {
        element.data.push({
          question: current.question,
          answer: current.answer
        });
      } else { // user_idが切り替わった時に新規の要素としてgroupDataに追加する
        result.push({
          id: current.user_id,
          name: current.user_name,
          email: current.user_email,
          data: [{
            question: current.question,
            answer: current.answer,
          }],
        });
      }
      return result;
    }, []);

DBから取得したデータをJSONに成形しました。reduce関数を使っているのは1クエリで適切な形でデータを取得できなかったためなので、必須ではありません。for文やmap関数を使ってきれいにしてください。
reduce関数でのデータをグループ化は以下を参考にさせて頂きました。


参考サイト
JavaScript オブジェクト配列をsqlのgroup byのように集計する


成形したデータは以下のようになりました。dataの個数は同じでなくても大丈夫です。

groupDataの中身
[
  {
    id: 267,
    name: "taro",
    email: "[email protected]",
    data:[
      {
        question: "好きなプログラミング言語は?",
        answer: ["HTML", "CSS", "JavaScript"]
      },
      {
        question: "得意なプログラミング言語は?",
        answer: ["HTML", "CSS"]
      },
      {
        question: "苦手なプログラミング言語は?",
        answer: ["JavaScript"]
      }
    ]
  },
  {
    id: 269,
    name: "jiro",
    email: "[email protected]",
    data:[
      {
        question: "好きなプログラミング言語は?",
        answer: ["Go", "JavaScript"]
      },
      {
        question: "得意なプログラミング言語は?",
        answer: ["PHP", "Ruby"]
      },
      {
        question: "苦手なプログラミング言語は?",
        answer: ["Java"]
      }
    ]
  },
]

2. JSONをCSVに変換

続いてJSONに成形したデータをCSVに変換していきます。今回はjson2csvというモジュールを使用しました。他にもCSV系のモジュールはたくさんありましたので、自分にとって使いやすいものや仕様に沿っているものを選択すると良いかと思います。

追記:2020/09/27

以前、json2csvで出力したCSVファイルがExcelで文字化けしてしまうという記事内で、withBOMオプションをつけるとExcelでも文字化けしなくなる、と書いていたのですがこの方法はExcel2016からが有効だったみたいで、それ以前のバージョンで文字化けしないようにするには単純にUTF-8ではなくShift-JISに文字コードを設定します。大変失礼いたしました。
Shift-JISへの変換はjconvというモジュールを使用しました。

get_csv.js
const {Parser, transforms: {unwind}} = require('json2csv');
const jconv = require('jconv'); // 追記:2020/09/27

function getCsv(data, fields, pathes, res) {
  const transforms = [unwind({
    paths: pathes,
    blankOut: true,
  })];
  const json2csvParser = new Parser({fields, transforms});
  const csv = data.length ? json2csvParser.parse(data) : '';
  res.setHeader('Content-disposition', 'attachment; filename=data.csv');
  res.setHeader('Content-Type', 'text/csv; charset=Shift_JIS');
  return (jconv.convert(csv, 'UTF8', 'SJIS')); // 追記:2020/09/27
}

module.exports = getCsv;

引数のdataは先程成形したデータが渡ってきます。fieldsにはdataの中の表示させたい項目を配列で渡します。今回の例ですと、以下のようになります。

fieldsの中身
const fields = ['name', 'email', 'data.question', 'data.answer'];

questionとanswerはdataの入れ子になっているのでドット繋ぎにします。

最後のpathesはdataの中でネストされている項目を配列で渡します。今回の例では以下のようになります。

pathesの中身
const pathes = ['data']

ここで、pathesの中にdata.answerを入れると、answerに格納されている配列が展開されて表示されるようになります。今回は、一つの設問に対して複数の回答を1行で表示させたかったのでdataのみにしました。
他にも色々オプションがありますので、詳しくは公式ドキュメントを参照してください。

3. CSVの出力

後は、CSVに変換したデータをPOSTなどのルーティングでres.sendしてあげることで、ブラウザからファイルをダウンロードすることができます。

CSV出力
const csv = getCsv(groupData, fields, pathes, res);
res.send(csv);

以上です。