Validate Unicode with isWellFormed in JavaScript

This article provides an overview of how to validate Unicode strings in JavaScript using the String.prototype.isWellFormed() method. You will learn what makes a string well-formed or ill-formed in UTF-16, why lone surrogates cause runtime errors, and how to use this method to verify string safety before encoding or transmitting data.


Understanding Well-Formed Unicode in JavaScript

JavaScript strings are sequences of 16-bit code units encoded in UTF-16. Characters outside the Basic Multilingual Plane (BMP), such as emojis and rare symbols, are represented using pairs of 16-bit code units known as surrogate pairs: * Leading (high) surrogates: Code units in the range 0xD800 through 0xDBFF. * Trailing (low) surrogates: Code units in the range 0xDC00 through 0xDFFF.

A string is well-formed if every high surrogate is immediately followed by a low surrogate, and every low surrogate is immediately preceded by a high surrogate. If a string contains an isolated, unmatched surrogate—referred to as a lone surrogate—it is considered ill-formed.

Using String.prototype.isWellFormed()

The isWellFormed() method checks whether a string contains any lone surrogates. It returns a boolean: true if the string contains only well-formed Unicode, and false otherwise.

Syntax

str.isWellFormed()

Example Usage

// Standard strings are well-formed
const ascii = "Hello, world!";
console.log(ascii.isWellFormed()); // true

// Properly paired surrogates (e.g., emojis) are well-formed
const emoji = "🚀"; // Code point U+1F680 represented as \uD83D\uDE80
console.log(emoji.isWellFormed()); // true

// Lone leading surrogate
const loneHigh = "ab\uD800c";
console.log(loneHigh.isWellFormed()); // false

// Lone trailing surrogate
const loneLow = "ab\uDFFFc";
console.log(loneLow.isWellFormed()); // false

Why Validating Unicode Matters

Certain built-in JavaScript functions and Web APIs fail or throw errors when encountering ill-formed strings:

  1. encodeURI() and encodeURIComponent(): These functions throw a URIError when passed a string containing lone surrogates.
  2. Web APIs: Methods such as structuredClone() or operations sending data to external databases and network endpoints may fail or corrupt data when handling unmatched surrogates.

Preventing Runtime Errors

Using isWellFormed() allows you to sanitize or bypass invalid input before running operations that require well-formed Unicode:

function safeEncode(url) {
  if (!url.isWellFormed()) {
    console.warn("Invalid string detected. Encoding aborted.");
    return null;
  }
  return encodeURI(url);
}

const invalidUrl = "https://example.com/\uD800";
safeEncode(invalidUrl); // Logs warning and returns null instead of throwing URIError

Fixing Ill-Formed Strings with toWellFormed()

If an ill-formed string needs to be repaired rather than rejected, JavaScript provides the complementary String.prototype.toWellFormed() method. This method creates a new string where any lone surrogates are replaced by the Unicode replacement character (\uFFFD, ):

const badString = "test-\uD800-case";

if (!badString.isWellFormed()) {
  const fixedString = badString.toWellFormed();
  console.log(fixedString); // "test--case"
  console.log(fixedString.isWellFormed()); // true
}