JavaScript normalize(): choosing NFC or NFKC for string comparisons

JavaScript normalize(): choosing NFC or NFKC for string comparisons

Two names can look identical in a form yet fail a JavaScript equality check. A copied word may also miss a search result even though its visible letters appear unchanged. The underlying Unicode sequence is a useful place to look.

For example, the Japanese syllable “が” can be represented by one code point or by “か” followed by a combining voiced mark. String.prototype.normalize() can bring equivalent sequences into a common form for comparison. Choosing NFC or NFKC also determines which distinctions your application keeps.


Compare strings that look the same

Run this example in a browser’s developer console or Node.js. The \u304C notation is a Unicode escape. The two strings represent the same syllable, but contain different sequences.

const composed = "\u304C"; // が (ga)
const decomposed = "\u304B\u3099"; // か + combining voiced mark

console.log(composed === decomposed);
console.log(composed.length, decomposed.length);
console.log(composed.normalize("NFC") === decomposed.normalize("NFC"));
false
1 2
true

The original strings compare as false, and their length values are 1 and 2. Normalizing both with NFC makes the last comparison true. Here, length counts UTF-16 code units; it is not a count of visible characters. A font’s rendering and string equality are separate concerns.

Create a separate comparison key with NFC

NFC applies canonical decomposition followed by canonical composition. Apply the same form to both the input and the value you compare it with. Calling normalize() without an argument also uses NFC, but naming the form makes the intended comparison easier to read.

const original = "か\u3099く";
const searchKey = original.normalize("NFC");

console.log(original.length);
console.log(searchKey);
console.log(searchKey.length);
console.log(original === searchKey);
3
がく
2
false

normalize() returns a string rather than changing original. This example keeps the entered spelling and creates searchKey separately. For names and display names, that separation lets you preserve the original value while choosing how search comparisons behave.

Normalization can change the string’s length. Align the counting unit and the point at which limits are checked with your server’s rules. The guide to counting characters in JavaScript compares code units, code points and grapheme clusters.

Check what NFKC changes

NFKC also applies compatibility decomposition. It can bring fullwidth Latin letters or circled digits into a simpler form, which may be useful for search. It can also remove distinctions that matter in the original text.

for (const text of ["ABC", "①", "髙", "高", "𠮷", "吉"]) {
  console.log(text, text.normalize("NFC"), text.normalize("NFKC"));
}
ABC ABC ABC
① ① 1
髙 髙 髙
高 高 高
𠮷 𠮷 𠮷
吉 吉 吉

In these examples, NFKC changes “ABC” to “ABC” and “①” to “1”. It does not make “髙” and “高”, or “𠮷” and “吉”, equal. Normalization is not a general mapping of every visually similar character or name variant.

For a name field, decide which differences search should ignore while keeping the spelling the person entered. If a supplementary-plane character such as “𠮷” disappears during storage, inspect the database character set and the text-processing path before replacing it with another character.

Choose a form for the field’s purpose

Purpose Approach to consider
Compare canonically equivalent combining sequences Normalize both comparison values with NFC
Make search ignore distinctions such as fullwidth Latin letters Inspect NFKC’s conversions, then decide whether to use it for search keys
Keep the entered spelling of a name or display name Preserve the original and create a separate comparison key when needed

NFC and NFKC do not fold letter case, trim surrounding whitespace or make HTML safe to display. Treat those requirements as separate parts of the field’s design. Adding normalization to passwords or data used for signature verification changes the matching rules, so it needs to follow the authentication or protocol specification rather than being inserted into an existing comparison.

Specification references

このブラウザからの自分のアクセスを解析から除外できます。