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

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

### 使用したモジュール
・json2csv
https://www.npmjs.com/package/json2csv

## 開発
### 1. データの成形

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

```js:データを成形する
    // 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のように集計する](https://zukucode.com/2017/05/javascript-object-sql-group-by.html)

---

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

```json-doc:groupDataの中身
[
  {
    id: 267,
    name: "taro",
    email: "taro@example.com",
    data:[
      {
        question: "好きなプログラミング言語は？",
        answer: ["HTML", "CSS", "JavaScript"]
      },
      {
        question: "得意なプログラミング言語は？",
        answer: ["HTML", "CSS"]
      },
      {
        question: "苦手なプログラミング言語は？",
        answer: ["JavaScript"]
      }
    ]
  },
  {
    id: 269,
    name: "jiro",
    email: "jiro@example.com",
    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で文字化けしてしまう](https://qiita.com/kum44/items/c6fbf7bbb60b95aaa896)という記事内で、`withBOM`オプションをつけるとExcelでも文字化けしなくなる、と書いていたのですがこの方法はExcel2016からが有効だったみたいで、それ以前のバージョンで文字化けしないようにするには単純にUTF-8ではなくShift-JISに文字コードを設定します。大変失礼いたしました。
Shift-JISへの変換は`jconv`というモジュールを使用しました。

```js: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の中の表示させたい項目を配列で渡します。今回の例ですと、以下のようになります。

```js:fieldsの中身
const fields = ['name', 'email', 'data.question', 'data.answer'];
```
questionとanswerはdataの入れ子になっているのでドット繋ぎにします。

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

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

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

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

![出力結果](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245419/48a924b8-066a-d1a5-d8ff-12fbaa9eb4ee.jpeg)

以上です。