Node.js で BOM付きの UTF8 を扱う
Node.js を使って HTTPS の GET メソッドで JSON ファイルを取得して JSON.parse()
すると次のようなエラーが発生しました。
Uncaught SyntaxError: Unexpected token in JSON at position 0
JSONデータの1文字目から何かがおかしいと言われています。いろいろ調べてみると原因は文字コードが BOM 付きの UTF8 になっているせいでした。バイナリエディタなどでデータを確認して、先頭3バイトが EF BB BF
になっていると BOM付きUTF8 のデータだとわかります。
BOM付きの文字列データだと基本はBOMなしのデータと同じですが、先頭に BOM のデータがついているという違いがあります。この先頭バイトが JSON.parse()
で不正なトークンとして処理されてエラーになっていました。
結論まとめ
// BOMを無視する
if (text.charCodeAt(0) === 0xFEFF) {
text = text.substr(1);
}
BOMのせいなら上のコードで先頭文字を無視すれば何とかなります。以下その対処方法詳細です。
そもそも JSON は BOM を付けてはいけない規格?
RFC 7159 – The JavaScript Object Notation (JSON) Data Interchange Format
調べてみるとどうもJSONの規格的に BOM を付けるのはNGみたいなんですが、利用するAPIにはなぜが BOM がついてました。そのような場合は、仕方ないのでクライアント側の実装で回避しましょう。
BOMを無視して文字列を扱う
Javascript の文字列だと BOM がついていると、先頭文字が 0xFEFF
になっているので?これを無視するように処理すると、うまくいきます。charCodeAt()
で文字列から任意の位置の文字のコード値を取得できるので、これを使って先頭文字が BOM であれば substr(1)
で先頭文字をちぎって残りを使うようにすればOKです。
// BOM付のデータだと文字列がこんな感じになっている
let text = String.fromCharCode(0xFEFF) + '{"a": 1}';
// BOMを無視する
if (text.charCodeAt(0) === 0xFEFF) {
text = text.substr(1);
}
// BOMを処理できてたらパースできる
let json = JSON.parse(text);
console.log(json);
// { a: 1 }
Node.js の http で GET した BOM付きUTF8 データを JSON.parse() する
APIが返すデータは BOM付きUTF8 で先頭3バイトが EF BB BF
でしたが、以下のようにして https
モジュールでリクエストした結果を文字列にして処理したら先頭文字が 0xFEFF
になってました。
Javascriptの内部文字コードが UTF32 だから?
いずれにせよ BOM付きUTF8 は以下のようにして JSON.parse()
できます。
const https = require('https');
// BOM付のデータの制御
const req = https.request('https://example.com', (res) => {
let body = '';
res.on('data', (chunk) => {
body += chunk;
});
res.on('end', () => {
// 文字列の1文字目が0xFEFFならBOMなので無視
if (body.charCodeAt(0) === 0xFEFF) {
body = body.substr(1);
}
let json = JSON.parse(body);
console.log(json);
});
});
req.end();
node-fetch で GET した BOM付きUTF8 データを JSON.parse() する
node-fetch とは、Node.js で ブラウザの fetch
を使えるようにするためのライブラリです。
json()
を呼べば直接JSONオブジェクトとしてパースしてくれますが、BOMが絡むとエラーになってしまいます。なので BOM が付いてくる API なら一度 text()
で受けて BOM を削除してから JSON.parse()
しましょう。
const res = await fetch('https://example.com');
let text = await res.text();
if (text.charCodeAt(0) === 0xFEFF) {
text = text.substr(1);
}
const json = JSON.parse(text);
// json() を呼ぶと、BOMがあるとエラーになる
// let json = await res.json();
そもそも BOM を付けてある JSON が規格的に間違った JSON なのでライブラリで制御するのはできないようです。
ブラウザの fetch だと何もせずにうまくいく
const res = await fetch('https://example.com');
let json = await res.json();
console.log(json);
ブラウザの fetch()
だと何もせずとも json()
を呼べばいい感じにパースできました。
以上。
コメントを書く