The String normalize() method
By Flavio Copes
Learn how the JavaScript normalize() method returns a string converted to a Unicode normalization form like NFC, NFD, NFKC or NFKD, with NFC as the default.
The normalize() method returns the string converted to a Unicode normalization form. You reach for it when two strings look identical on screen but don’t compare as equal.
Why two identical strings can be different
Unicode often has more than one way to represent the same character. The é in “café” can be a single code point, or a plain e followed by a combining accent:
const single = 'caf\u00e9'
const combined = 'cafe\u0301'
single //'café'
combined //'café'
single === combined //false
single.length //4
combined.length //5
Same text for a human, different data for JavaScript. String comparison in JavaScript checks code points, not what the reader sees. This bites you with user input: text typed on one system can arrive in the decomposed form, while the value you stored is composed.
How normalize() fixes this
Convert both strings to the same form before comparing:
single.normalize() === combined.normalize() //true
Called without arguments, normalize() uses the NFC form, which composes characters into their single code point version when one exists.
The four normalization forms
Unicode has four main normalization forms. Their codes are NFC, NFD, NFKC, NFKD. Wikipedia has a good explanation of the topic.
NFC composes, NFD decomposes. They are two representations of the same text, and you can convert back and forth without losing anything:
'caf\u00e9'.normalize('NFD').length //5
The K forms, NFKC and NFKD, are compatibility normalizations. They also fold characters that mean the same thing but look different, like the fi ligature:
'\uFB01' //'fi'
'\uFB01'.normalize('NFKC') //'fi'
'\uFB01'.normalize('NFC') //'fi'
NFC leaves the ligature alone, because it’s already a valid composed character. NFKC rewrites it as the two letters f and i. This is a one-way trip, there’s no way back to the ligature, so use the K forms for matching and searching, not for storing text.
A pitfall to watch for
Comparing or looking up strings without normalizing first. A username search that fails for “José”, a duplicate check that lets the same tag in twice. The strings were equal to every human who looked at them.
The fix: normalize at the boundaries of your app. Call .normalize() on user input before you store it or compare it, and you only deal with one representation everywhere else.