[Node.js] BOM付きUTF8のデータを処理する方法(node-fetch, http)

[Node.js] BOM付きUTF8のデータを処理する方法(node-fetch, http)

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 のデータだとわかります。

バイト順マーク – Wikipedia

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() を呼べばいい感じにパースできました。

以上。

参考URL

Javascriptカテゴリの最新記事