JavaScriptのisWellFormed():孤立サロゲートを検出して文字列の破損を見つける
String.prototype.isWellFormed()で不正なUTF-16文字列を検出する方法を解説。引数・戻り値、ブラウザーとNode.jsの実行手順、期待出力、入力検証の実例と判定の限界を学べます。
文字列を途中で切り出したあと、URL用に変換しようとすると例外が発生することがあります。原因の一つが、絵文字などを表すUTF-16のペアが分断され、片方だけ残ることです。
String.prototype.isWellFormed()は、このような孤立サロゲートが文字列に含まれていないかを確認するメソッドです。この記事では、文字列の破損を検出し、不正な入力を処理の入口で拒否する使い方を説明します。
何を検出できるのか
JavaScriptの文字列はUTF-16のコード単位で扱われます。日本語の「あ」は1コード単位ですが、絵文字の「😀」は2コード単位で表されます。この2つの組をサロゲートペアと呼びます。
ペアの前半だけ、または後半だけが残ったものが孤立サロゲートです。JavaScriptはその状態の文字列も保持できますが、外部向けの変換処理では問題になることがあります。
たとえば、"😀".slice(0, 1)は見た目の1文字を取り出す処理にはなりません。UTF-16の先頭1コード単位だけを残すため、ペアを壊します。
isWellFormed()は文字数を数えたり、文字列を書き換えたりせず、孤立サロゲートの有無を真偽値で返します。文字の構造についてはMDNのString解説に説明があります。
動作環境と対応バージョン
2026年11月8日時点で確認した公式資料では、次のバージョンから利用できます。
- Chrome 111以降
- Edge 111以降
- Firefox 119以降
- Safari 16.4以降
- Node.js 20.0.0以降
ブラウザーの対応はweb.devの2023年10月の機能紹介、Node.jsへの導入はNode.js 20.0.0のリリースノートで確認できます。MDNでは2023年10月から主要ブラウザー間で利用可能な機能として案内されています。
このAPIはブラウザーとNode.jsの両方で動きます。DOMや外部パッケージは不要です。ただし、APIの導入バージョンと製品の保守期間は別です。新しく実行環境を用意するときは、保守中のNode.jsを選んでください。
未対応環境の確認
メソッドがない環境で直接呼び出すとTypeErrorになります。実行前には次のように確認できます。
console.log(typeof String.prototype.isWellFormed === "function");対応環境ならtrueです。後述のサンプルはこの確認を組み込み、未対応環境では説明を表示して処理を終えます。機能がない場合に、検証を省略して正常扱いする設計は避けましょう。
引数・戻り値と基本構文
基本構文はtext.isWellFormed()です。検査する文字列が呼び出し元になり、引数はありません。
- 孤立サロゲートがなければ
trueを返します。 - 孤立サロゲートが一つでもあれば
falseを返します。 - 空文字列も
trueです。 - 元の文字列は変更しません。
不正な文字列そのものを理由に例外を投げるAPIではありません。入力が文字列であることを確認してから呼べば、判定結果に応じて拒否や通知を選べます。構文と戻り値はMDNのisWellFormed()リファレンスに記載されています。
型の検証も必要
nullや数値を受け取る可能性がある処理では、先にtypeof value === "string"を確認します。すべての値がこのメソッドを持つわけではありません。
先にString(value)で変換すると、nullは文字列の"null"になります。それを受理してよいかは業務上の判断です。ここでは、文字列以外を明確に拒否します。
コピーして動かす入力検証サンプル
次のコードは、通常の文字列、正しい絵文字、孤立サロゲート、文字列以外の入力を検査します。外部通信やファイル読み込みは行いません。
inspectText()は、この例で用意する独自関数です。isWellFormed()の結果を使い、表示用の判定文字列を返します。
(() => {
"use strict";
if (typeof String.prototype.isWellFormed !== "function") {
console.log("この環境はisWellFormed()に対応していません");
return;
}
function inspectText(value) {
if (typeof value !== "string") {
return "拒否: 文字列ではありません";
}
if (!value.isWellFormed()) {
return "拒否: 孤立サロゲートがあります";
}
return "受理";
}
const samples = [
{ label: "日本語", value: "こんにちは" },
{ label: "絵文字", value: "\uD83D\uDE00" },
{ label: "空文字", value: "" },
{ label: "前半だけ", value: "\uD83D" },
{ label: "後半だけ", value: "\uDE00" },
{ label: "途中で切断", value: "\uD83D\uDE00".slice(0, 1) },
{ label: "数値", value: 123 },
];
for (const { label, value } of samples) {
console.log(`${label}: ${inspectText(value)}`);
}
})();ブラウザーでの実行方法
対応ブラウザーで空のタブを開き、開発者ツールのConsoleを選びます。上のJavaScriptコード全体を貼り付け、実行してください。HTMLファイルや開発サーバーを用意する必要はありません。
Node.jsでの実行方法
読者の手元でコードをwell-formed-demo.jsとしてUTF-8で保存します。ターミナルでそのファイルを保存したフォルダーへ移動し、次を実行してください。ライブラリのインストールやpackage.jsonの作成は不要です。
node --version
node well-formed-demo.js期待出力
対応環境では、コードから次の7行の出力が導かれます。ここに示す結果は仕様に基づく期待値であり、実機検証済みの記録ではありません。
日本語: 受理
絵文字: 受理
空文字: 受理
前半だけ: 拒否: 孤立サロゲートがあります
後半だけ: 拒否: 孤立サロゲートがあります
途中で切断: 拒否: 孤立サロゲートがあります
数値: 拒否: 文字列ではありません絵文字をUnicodeエスケープで記述しているのは、ペアの前半と後半をコード上で区別するためです。空文字の受理はAPIの仕様に合わせています。必須項目として使う場合は、空文字を拒否する条件を別途追加します。
失敗例:判定せずにURL用へ変換する
孤立サロゲートを含む文字列をencodeURIComponent()へ渡すとURIErrorが発生します。この条件はMDNのencodeURIComponent()リファレンスで確認できます。
次のコードは先ほどのサンプルとは独立して実行できます。同じ方法でConsoleへ貼り付けるか、別のJavaScriptファイルとしてNode.jsで実行してください。
(() => {
"use strict";
if (typeof String.prototype.isWellFormed !== "function") {
console.log("この環境はisWellFormed()に対応していません");
return;
}
const broken = "\uD83D\uDE00".slice(0, 1);
try {
encodeURIComponent(broken);
} catch (error) {
if (!(error instanceof URIError)) {
throw error;
}
console.log(`直接変換: ${error.name}`);
}
if (!broken.isWellFormed()) {
console.log("事前判定: 不正な文字列のため変換を中止");
} else {
console.log(encodeURIComponent(broken));
}
})();期待出力は次のとおりです。例外メッセージは実装によって異なるため、共通の例外名だけを表示しています。
直接変換: URIError
事前判定: 不正な文字列のため変換を中止isWellFormed()自体は同期処理で、Promiseを返しません。awaitや非同期の例外処理は不要です。この例では、変換時の失敗と事前判定の違いを示すためにtry...catchを使っています。
判定の限界と実務での活用
正常判定でも内容の妥当性は保証しない
trueは孤立サロゲートがないことだけを意味します。文字化けした文章、制御文字、見た目が似た文字、不適切な入力も、孤立サロゲートがなければ通過します。
また、表示上の文字数、文字列の正規化、HTMLへの安全な出力、URLの許可判定は別の問題です。このAPIだけで入力検証を完結させないでください。
検査を置く場所を決める
活用しやすいのは、外部データの取り込み時や、独自の文字列加工を終えた直後です。たとえば、インポートした商品名を検査し、不正な行だけを修正対象として返す処理に使えます。
検査後にコード単位で切り出すと、正常だった文字列を再び壊す可能性があります。検査を入口に置くだけでなく、変換先へ渡す最終的な文字列にも適用する設計が有効です。
不正な入力を検出したら、元データの再取得や修正を促す方針を決めます。検出だけでは失われたペアを復元できません。利用者の入力を黙って変更しないことにも意味があります。
改善策として、拒否理由と対象レコードの識別子を返す形にすると、取り込み処理の修正作業につなげやすくなります。本文全体をログへ出さずに問題箇所を追えるため、運用上も扱いやすくなります。
公式参考資料
Googleの優先するニュース提供元にSEの部屋を追加
Googleで、いつも読みたい情報源を選べます。登録可否はGoogleの画面で確認できます。
候補にSEの部屋が表示されない場合は、まだ追加できません。