EC事業部エンジニアのharashoです。この記事はEC事業部ブログリレーの13日目の記事で、12日目は@ku00さんによるカラーミーショップの一機能をAngular Elementsで実装しましたでした。
タイトルがこの記事の結論になりますが、私が遭遇したCSVファイルの読み込み処理で起きた不具合と原因、対応方法について書きます。
CSVファイルの読み込み処理で起きた不具合
私が開発に携わったカラーミーリピートの一括発送アプリには、注文に対する発送リストをCSVファイルから一括登録して、発送処理を行う機能があります。以下はその機能の簡単な流れと、CSVファイルのイメージです。
1.CSVテンプレートをダウンロード
2.テンプレートを元に編集
3.編集したCSVファイルをアップロード
4.発送処理を実行
CSVテンプレート(サンプル)
"名前","注文ID","配送会社","伝票番号"
"ペパボ 太郎","Pi0gO9M","",""
"ペパボ 一郎","12DieQ1","",""
"ペパボ 花子","jN3rE8R","",""
編集したCSVファイル(サンプル)
"名前","注文ID","配送会社","伝票番号"
"ペパボ 太郎","Pi0gO9M","1","123456777"
"ペパボ 一郎","12DieQ1","2","123456888"
"ペパボ 花子","jN3rE8R","3","123456999"
この機能を使う際に、ダウンロードしたCSVテンプレートをExcelで編集し、UTF-8(コンマ区切り)形式で保存したCSVファイルをアップロードするとエラーが起きることが分かりました。
不具合の原因
調査した結果、CSVファイルのカラム名を変換する処理で、パースしたヘッダーのカラム名がテンプレートのヘッダーのカラム名と一致せず、エラーが起きていることが分かりました。
CSVのカラム名を変換する処理(サンプル)
switch (header) {
case '名前': // カラム名はテンプレートと同じカラム名を期待している
return 'name'
// 省略
default:
throw new Error('ヘッダーが不正です。')
}
headerに入っている値を確認したところ、ヘッダーの最初のカラム名が、一見同じ文字列だが同じではない不思議な状態でした。
> header
"名前"
> header === "名前"
false
文字列を詳しく見るためにこの値をURIエンコードしてみると、先頭に謎の文字列 %EF%BB%BF
が入っていました。これは、BOM(Byte Order Mark)というものでした。この文字列が原因でパースしたヘッダーの最初のカラム名とテンプレートのヘッダーのカラム名が一致していなかったのです。
> encodeURI(header)
'%EF%BB%BF%E5%90%8D%E5%89%8D'
> encodeURI("名前")
'%E5%90%8D%E5%89%8D'
BOMについて
バイト順マーク (バイトじゅんマーク、英: byte order mark) あるいはバイトオーダーマークとは、通称BOM(ボム)といわれるUnicodeの符号化形式で符号化したテキストの先頭につける数バイトのデータのことである。このデータを元にUnicodeで符号化されていることおよび符号化の種類の判別に使用する。
出典:バイト順マーク - Wikipedia
今回のケースではExcelのCSVファイルの保存形式によってBOMが付いていたようです。
対応方法: BOMを取り除く
CSVファイルにBOMが付いていても問題なく処理を行えるほうが、手間が掛からず便利です。ですので、先頭の文字列にBOMが存在する場合は取り除く処理を追加しました。sindresorhus/strip-bomというライブラリを参考に、以下のようなコードをJavaScriptで書きました。
const stringWithoutBom = csvString.charCodeAt(0) === 0xFEFF ? csvString.slice(1) : csvString
おわりに
この記事ではCSVファイルの読み込み処理で、BOMにより起きた不具合と原因、その対応方法について説明しました。私自身、今回の対応からCSVファイルのようなテキストファイルを取り扱うときはBOMの存在に注意したほうが良いことを学びました。同じ不具合に遭遇している方やCSVの読み込み処理を実装する方の参考になれば嬉しいです。