3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ブラウザでCSVファイルを読み込む方法 Part2:encoding-japaneseで文字化けした話

3
Last updated at Posted at 2026-02-12

はじめに

前回の記事では、ブラウザでCSVファイルを読み込む実装を紹介しました。
文字コードの検出・変換には encoding-japanese ライブラリを使用していましたが、文字化けが発生する問題がありました😔

この記事では、文字化けの原因と対応について紹介します。

💡 この記事でわかること

  • encoding-japaneseでCP932の拡張文字が文字化けする理由
  • Shift_JISとCP932(Windows-31J)の違い
  • TextDecoderを使った文字コード変換の実装

発生した問題

「﨑」などの文字を含むCSVファイルを読み込むと、文字コードの検出・変換に失敗して、文字化けする

前回の実装では、encoding-japaneseの detect で文字コードを判定し、convert でUnicodeに変換していました。

import Encoding from 'encoding-japanese';

const detectedEncoding = Encoding.detect(uint8Array);
const unicodeArray = Encoding.convert(uint8Array, {
    to: 'UNICODE',
    from: detectedEncoding,
});
const unicodeStr = Encoding.codeToString(unicodeArray);

detectconvertcodeToString で文字コードの判定と変換を行っています。

原因

encoding-japanese が CP932 の拡張文字に対応していない

原因は、Encoding.detect() がCP932拡張文字のバイト列を不正とみなし、文字コードを正しく判定できないことでした。

encoding-japaneseのGitHubリポジトリにもこの問題についてIssueが報告されています。

CP932形式とは、MicrosoftによるShift_JISの独自拡張です。特殊文字用に0xFA40以降を拡張して使用するため、Shift_JISとしてdetectしようとした場合検出に失敗することがあります。— encoding.js Issue #54


今回のケースでは、Encoding.detect()'UNICODE' を返していたため、Shift_JISのバイト列がUnicodeとして解釈され、文字化けが発生しました。

具体的には、CP932でエンコードされた「宮﨑」のバイト列を変換すると、以下のような結果になります。

Encoding.convert(from: 'SJIS') → 宮?(「﨑」が ? になる)
TextDecoder('shift_jis')       → 宮﨑(正しくデコード)

「﨑」はCP932の拡張領域に含まれる文字であり、Shift_JIS(JIS X 0208)の範囲外にあたるため、encoding-japaneseでは変換できません。


⚠️ 注意

仮に検出を修正して from: 'SJIS' を明示的に指定しても、Encoding.convert() もCP932拡張文字の変換に対応していないため、文字化けの問題は解消されません。

Shift_JISとCP932の違い

Shift_JISは JIS X 0201 と JIS X 0208 の文字集合をサポートする文字コードです。

一方、日本語Windows環境で使用される「Shift_JIS」は、MicrosoftによるShift_JISの拡張であり、正式名称は Microsoft Windows Codepage : 932(CP932)です。

CP932はShift_JISの文字集合に加えて、NEC特殊文字、NEC選定IBM拡張文字、IBM拡張文字をサポートしています。

Shift_JIS : JIS X 0201 + JIS X 0208
CP932 : JIS X 0201 + JIS X 0208 + NEC特殊文字 + NEC選定IBM拡張文字 + IBM拡張文字

CP932で追加された拡張文字の例

カテゴリ 文字例
NEC特殊文字 ①、②、Ⅰ、Ⅱ、㍉、㌔ など
NEC選定IBM拡張文字 纊、褜、鍈、彅 など
IBM拡張文字 﨑、髙、德、彅 など

WindowsやExcelで「Shift_JIS」と呼ばれているものは、実際はCP932のことです。
今回文字化けした「﨑」はIBM拡張文字に属しており、JIS X 0208には含まれていません。encoding-japaneseはShift_JIS(JIS X 0208)の範囲で処理を行うため、これらの拡張文字を正しく扱えませんでした。

対応方法

encoding-japaneseの detect + convert を、ブラウザ標準APIの TextDecoder に置き換えました。

TextDecoder

ブラウザの TextDecoder の仕様では、shift_jis と windows-31j は同一のエンコーディングとして扱われます。

shift_jiswindows-31j など、オプションとして異なるラベルを指定しても、ブラウザ内部では同一のデコーダが使われます。

new TextDecoder('shift_jis').encoding;   // 'shift-jis'
new TextDecoder('windows-31j').encoding; // 'shift-jis'

TextDecoder にはエンコーディングの自動検出機能がありません。
今回の実装では、UTF-8かShift_JIS(CP932)かを判定することにしました。

fatalオプションによる文字コード判定

fatal: true

不正なバイト列をデコードした時、TypeError がスローされます。

const decoder = new TextDecoder('utf-8', { fatal: true });
decoder.decode(new Uint8Array([0x82, 0xa0])); // TypeError(Shift_JISの「あ」)

fatal: false(デフォルト)

不正なバイト列は U+FFFD(�)に置換され、エラーになりません。

const decoder = new TextDecoder('utf-8');
decoder.decode(new Uint8Array([0x82, 0xa0])); // '��'

この挙動を利用して、UTF-8でのデコードに成功すればUTF-8、TypeError がスローされればShift_JIS(CP932)としてデコードし直します。

try {
    const decoder = new TextDecoder('utf-8', { fatal: true });
    return decoder.decode(uint8Array);
} catch {
    const decoder = new TextDecoder('shift_jis');
    return decoder.decode(uint8Array);
}

ignoreBOMオプションによるBOM除去

前回は、BOMを考慮する場合、削除するコードを実装する必要がありました。

TextDecoderignoreBOM オプションでBOMを無視するかどうかを設定できます。

ignoreBOM の値 動作
false(デフォルト) BOMを出力から除去する
true BOMを出力に含める

今回の実装では、BOM付きUTF-8ファイルにも対応したいので、デフォルトを採用しています。

実装コード

ここまでの内容を踏まえた最終的な実装コードです。

/** CSVファイルを読み込んで文字列に変換 */
export const readCsvFile = async (file: File): Promise<string> => {
    const arrayBuffer = await file.arrayBuffer();
    const uint8Array = new Uint8Array(arrayBuffer);
    try {
        const decoder = new TextDecoder('utf-8', { fatal: true });
        return decoder.decode(uint8Array);
    } catch {
        const decoder = new TextDecoder('shift_jis');
        return decoder.decode(uint8Array);
    }
};

⚠️ 注意

この実装はUTF-8とShift_JIS(CP932)の2つのみを判定対象としています。
他の文字コードを対象とする場合は別途対応が必要です。

まとめ

  • encoding-japaneseはCP932拡張文字の検出・変換に失敗する
  • Shift_JISとCP932は別物で、CP932はShift_JISの拡張版
  • TextDecoder('shift_jis') はCP932相当のデコードを行う

参考

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?