JavaScript Tips | 文字列ユーティリティ:業務用 - 文字コード判定

JavaScript JavaScript
スポンサーリンク

何をしたいユーティリティか:「文字コード判定」

ここでの「文字コード判定」は、外部から受け取った「バイト列」が、UTF-8 なのか Shift_JIS なのか EUC-JP なのか、といった「エンコーディング(文字コード)」を推測する処理です。
ブラウザや Node.js の中で扱う文字列は基本的に「JavaScript の内部表現(UTF-16 系)」ですが、ファイルや HTTP レスポンスを「バイト列」として受け取るときは、どの文字コードで書かれているかを知らないと正しく文字列に変換できません。


まず前提を整理する:文字列とバイト列

JavaScript の「文字列」はすでにデコード済み

"あいう" のような JavaScript の文字列は、すでに「文字コードが解釈された後」の世界です。
つまり、「この文字列が Shift_JIS か UTF-8 か」を判定することはできませんし、そもそも意味がありません。
文字コード判定の対象になるのは「バイト列(Uint8Array や ArrayBuffer)」です。

判定したいのは「バイト列 → 文字列」に変換する前

典型的な流れはこうです。

  1. ファイルやレスポンスをバイト列として受け取る。
  2. そのバイト列の文字コードを推測する。
  3. 推測した文字コードを使って「バイト列 → JavaScript 文字列」に変換する。

この「2. 文字コードを推測する」が、ここで作りたいユーティリティの役割です。


実務では「自前で全部やろうとしない」が正解

文字コード判定は本質的に難しい

UTF-8、Shift_JIS、EUC-JP、ISO-2022-JP…
日本語を扱う文字コードだけでも複数あり、バイト列だけを見て「これは絶対に Shift_JIS」と言い切るのは、かなり難しい問題です。
そのため、実務では「実績のあるライブラリを使う」のが現実的です。

encoding-japanese(encoding.js)のようなライブラリ

有名どころとして、encoding-japaneseencoding.js)というライブラリがあります。
このライブラリは、バイト配列から文字コードを推測する detect 関数と、文字コード変換を行う convert 関数を提供しており、ブラウザでも Node.js でも使えます。 Github GeSource


ライブラリを使った「文字コード判定」ユーティリティ

基本的な使い方のイメージ

encoding-japanese を読み込んだ前提で、バイト列の文字コードを判定するユーティリティを作ってみます。

// 例: Encoding は encoding-japanese のグローバル or import 済みオブジェクト
// const Encoding = require("encoding-japanese");

function detectEncoding(bytes) {
  if (!bytes) {
    return null;
  }

  const arr = bytes instanceof Uint8Array ? Array.from(bytes) : bytes;

  const detected = Encoding.detect(arr);

  return detected || null;
}
JavaScript

ここでは、Uint8Array でも普通の配列でも受け取れるようにしつつ、
内部的には「数値配列([0xE3, 0x81, …] のような形)」にそろえています。

判定結果を使って文字列に変換する

判定した文字コードを使って、バイト列を JavaScript 文字列に変換するユーティリティもセットで用意すると便利です。

function decodeWithAutoDetect(bytes) {
  if (!bytes) {
    return "";
  }

  const arr = bytes instanceof Uint8Array ? Array.from(bytes) : bytes;

  const encoding = Encoding.detect(arr);

  const unicodeArray = Encoding.convert(arr, {
    from: encoding,
    to: "UNICODE",
  });

  return Encoding.codeToString(unicodeArray);
}
JavaScript

これで、「バイト列を渡すだけで、文字コードを自動判定して文字列にしてくれる」関数になります。


TextDecoder と「判定済みの文字コード」

TextDecoder は「判定」ではなく「デコード」

ブラウザや Node.js には TextDecoder という標準 API があり、
new TextDecoder("utf-8") のように「文字コード名を指定して」バイト列を文字列に変換できます。

const decoder = new TextDecoder("utf-8");
const text = decoder.decode(uint8Array);
JavaScript

ここで重要なのは、TextDecoder は「文字コードを判定してくれるわけではない」という点です。
「すでに分かっている文字コード名」を渡して使うものなので、「判定」と組み合わせるなら、

  1. ライブラリなどで文字コードを推測する。
  2. 推測結果を TextDecoder に渡してデコードする。

という流れになります。


自前でやる「超ざっくり判定」の例(考え方の練習用)

UTF-8 っぽいかどうかだけを見る簡易チェック

本番で使うには心もとないですが、「文字コード判定のイメージ」をつかむために、
「このバイト列が UTF-8 として妥当かどうか」をざっくりチェックする関数を書いてみます。

function looksLikeUtf8(bytes) {
  const arr = bytes instanceof Uint8Array ? bytes : Uint8Array.from(bytes);

  let i = 0;
  while (i < arr.length) {
    const b = arr[i];

    if (b <= 0x7f) {
      i += 1;
    } else if (b >= 0xc2 && b <= 0xdf) {
      if (i + 1 >= arr.length) return false;
      const b2 = arr[i + 1];
      if (b2 < 0x80 || b2 > 0xbf) return false;
      i += 2;
    } else if (b >= 0xe0 && b <= 0xef) {
      if (i + 2 >= arr.length) return false;
      const b2 = arr[i + 1];
      const b3 = arr[i + 2];
      if (b2 < 0x80 || b2 > 0xbf || b3 < 0x80 || b3 > 0xbf) return false;
      i += 3;
    } else if (b >= 0xf0 && b <= 0xf4) {
      if (i + 3 >= arr.length) return false;
      const b2 = arr[i + 1];
      const b3 = arr[i + 2];
      const b4 = arr[i + 3];
      if (
        b2 < 0x80 || b2 > 0xbf ||
        b3 < 0x80 || b3 > 0xbf ||
        b4 < 0x80 || b4 > 0xbf
      ) {
        return false;
      }
      i += 4;
    } else {
      return false;
    }
  }

  return true;
}
JavaScript

これは「UTF-8 のバイト列として矛盾がないか」をチェックしているだけで、
「UTF-8 ではない」=「Shift_JIS だ」とまでは言えません。
ただ、「UTF-8 っぽいなら UTF-8 とみなす」「そうでなければ別のエンコーディング候補を試す」といった戦略の一部として使えます。


実務で意識してほしい設計のポイント

1. 「何を判定したいか」を絞る

「世界中のすべての文字コードを自動判定したい」と考えると、途端に難易度が跳ね上がります。
業務システムでは、まず「自分たちが実際に扱う可能性のある文字コード」を絞るのが現実的です。

例としては、次のような方針が多いです。

  • 原則 UTF-8。
  • 例外的に Shift_JIS(古いシステムや Excel 由来の CSV など)。
  • EUC-JP や ISO-2022-JP は必要なら追加で対応。

この「候補セット」が決まれば、ライブラリの判定結果をその中にマッピングするだけで済みます。

2. 「判定に失敗したとき」の扱いを決める

文字コード判定は確率的なものなので、「どうしても判定できない」「誤判定する」ケースは避けられません。
そのときにどう振る舞うかを、あらかじめ決めておくことが大事です。

例えば、

  • 判定できなければ UTF-8 とみなす。
  • 判定できなければエラーにして、ユーザーに文字コードを選ばせる。

など、システムの性質に合わせてルールを決めておきます。

3. 「判定」と「変換」をユーティリティに閉じ込める

アプリケーションのあちこちで、

const enc = Encoding.detect(bytes);
const unicode = Encoding.convert(bytes, { from: enc, to: "UNICODE" });
JavaScript

のようなコードを書き始めると、
あとから仕様を変えたいときに全体を直す必要が出てきます。

そうではなく、

export function detectEncoding(bytes) { ... }
export function decodeWithAutoDetect(bytes) { ... }
JavaScript

のようなユーティリティを 1 箇所に置き、
「文字コードに関することは必ずここを通す」と決めておくと、
仕様変更にも強く、コードも読みやすくなります。


少し手を動かして感覚をつかむ

ブラウザのコンソールや Node.js で、次のようなコードを試してみてください(Encoding が使える前提)。

// UTF-8 の「これはテストです。」に相当するバイト列
const utf8 = Uint8Array.from([
  0xE3, 0x81, 0x93, 0xE3, 0x82, 0x8C, 0xE3, 0x81, 0xAF,
  0xE3, 0x83, 0x86, 0xE3, 0x82, 0xB9, 0xE3, 0x83, 0x88,
  0xE3, 0x81, 0xA7, 0xE3, 0x81, 0x99, 0xE3, 0x80, 0x82,
]);

detectEncoding(utf8);
decodeWithAutoDetect(utf8);
looksLikeUtf8(utf8);
JavaScript

detectEncoding の結果、decodeWithAutoDetect の文字列、looksLikeUtf8 の真偽値を見比べて、
「バイト列 → 文字コード判定 → 文字列」という流れのイメージをつかんでみてください。

そのうえで、自分のプロジェクトに

export function detectEncoding(...) { ... }
export function decodeWithAutoDetect(...) { ... }
export function looksLikeUtf8(...) { ... } // 必要なら
JavaScript

のような関数を置き、
「外部から来たバイト列に触るときは、必ず“文字コード判定ユーティリティ”を通す」
というルールを作ってみてください。
それだけで、あなたのシステムの文字コード周りは、「なんとなく動いている」状態から、意図と一貫性を備えた業務レベルの設計に近づいていきます。

タイトルとURLをコピーしました