Intermédiaire 8 min de lecture 25 janvier 2025

ZWJ (Zero Width Joiner) : comprendre, détecter et corriger

Tout semble identique, mais votre tri change, une recherche échoue, un emoji se sépare en deux. Le ZWJ (U+200D) est un caractère invisible qui relie des glyphes. Utile pour former des ligatures et des séquences emoji, il peut aussi saboter vos parsers et vos comparaisons s'il se glisse là où vous ne l'attendez pas.

Qu'est-ce que le ZWJ ?

Le ZWJ (Zero Width Joiner, U+200D) est un caractère de formatage invisible qui demande au moteur de rendu de lier deux glyphes adjacents pour former une unité visuelle.

Voici les principales situations où le ZWJ intervient :

1 Ligatures et jonctions typographiques

Forcer ou préserver des ligatures/jonctions dans certains systèmes d'écriture.

"f\u200Di" (ZWJ entre f et i) • Devanagari/Indic : consonne\u200Dconsonne • Scripts complexes

2 Scripts et shaping Unicode

Influence directe sur la formation des glyphes en arabe, indiens et d'autres écritures.

U+200D (Joiner), U+200C (ZWNJ - Non-Joiner) • Catégorie Unicode: Cf (Format)

3 Emojis et séquences combinées

Le ZWJ relie des emojis pour former des pictos composés.

👩‍💻 = "👩" + ZWJ + "💻"
👨‍👩‍👧‍👦 = personnes + ZWJ entre chaque
🏳️‍🌈 = drapeau blanc + ZWJ + arc-en-ciel
🧑‍⚕️, 🧑‍🍳, 🧑‍🚀 = personne + ZWJ + métier

4 Pièges techniques

Invisible à l'œil nu, il perturbe tri, recherche, parsers et validations.

Présence dans des emails/URLs copiés
Rupture de mots-clés ou d'identifiants
Écarts entre rendu visuel et contenu réel

Problèmes classiques avec le ZWJ

Copier-coller depuis le web ou un mobile

Insère un ZWJ entre des caractères, rendant une adresse, un slug ou un identifiant invalide sans que cela se voie.

Tests unitaires qui échouent

Une chaîne contient un U+200D non prévu, les comparaisons exactes et hash changent.

Trim() ou strip() inefficace

Ces fonctions ignorent le ZWJ, qui n'est ni un espace ni un visible standard.

Regex inadaptées

Selon le moteur, \s ou \w ne ciblent pas U+200D (catégorie Cf), il passe au travers.

Exemple de problème courant :

# Deux emails semblent identiques mais diffèrent
string1 = "email@domain.com"
string2 = "email‍@domain.com" # Contient U+200D entre "l" et "@
assert string1 == string2 # ❌ Échec

Symptômes qui doivent vous alerter

🚨 Signaux d'alarme

!
Un diff git signale une différence mais rien ne saute aux yeux dans la ligne
!
Un emoji composé se sépare en plusieurs pictos sur certaines plateformes
!
Un validateur d'email/URL refuse une valeur qui paraît correcte
!
Le curseur semble "bloquer" à un endroit vide en se déplaçant caractère par caractère
!
Des parsers ou split() donnent des résultats incohérents avec l'affichage

Comment le détecter

Solution recommandée : Clean ASCII

Clean ASCII détecte automatiquement la présence de ZWJ (U+200D) dans vos chaînes. Il identifie l'emplacement exact et explique l'impact potentiel sur votre rendu et vos traitements.

✅ Détection automatique

ZWJ, ZWNJ, séquences emoji, caractères de formatage

📊 Analyse complète

Codepoint U+200D, positions précises, contexte des glyphes adjacents

🧹 Nettoyage automatique

Suppression ciblée du ZWJ hors cas légitimes, remplacement sécurisé

💾 Export propre

Texte nettoyé prêt pour vos parsers, CSV, APIs et bases

Autres méthodes de détection

Affichage dans l'éditeur

Activez "show invisibles" / "render whitespace" et l'affichage des caractères de format
Installez une extension qui met en évidence U+200D et U+200C

En ligne de commande (Unix)

# Rechercher explicitement U+200D (ZWJ)
grep -P "\x{200D}" fichier.txt
# Visualiser fins de ligne et caractères spéciaux
sed -n l fichier.txt
# Afficher les caractères de contrôle/format
cat -A fichier.txt
# Inspecter les codes hexadécimaux
hexdump -C fichier.txt | grep "e2 80 8d"

En code

JavaScript

Array.from(str).map((c,i)=>c==="\u200D"?i:null).filter(i=>i!==null)

Python

[i for i, c in enumerate(s) if c == "\u200D"]

Excel / Google Sheets

TROUVE(UNICHAR(8205); cellule)

Nettoyer et prévenir le ZWJ

🚀 Solution rapide avec Clean ASCII

Avant d'écrire des scripts, utilisez Clean ASCII pour repérer et supprimer les ZWJ non désirés tout en préservant les cas légitimes (emojis, scripts complexes).

Détection U+200D ciblée
Nettoyage intelligent
Export immédiat

Méthodes techniques avancées

🔧 Normaliser

Appliquez NFC/NFKC pour uniformiser; notez que le ZWJ (Cf) est conservé par la normalisation
Décidez d'une politique: autoriser ZWJ uniquement dans emojis/scripts ciblés
Uniformisez les fins de ligne et supprimez les BOM inutiles

🧹 Filtrer

Écrivez une fonction strip_zwj() qui retire U+200D hors contextes autorisés
Validez/sanitisez les entrées: emails, slugs, identifiants sans ZWJ
Bloquez les caractères de format Unicode non attendus (Cf) à l'ingestion

⚙️ Automatiser

Hook pre-commit: refuser des diffs contenant U+200D non justifiés
Tests de sanitation sur les formulaires et APIs: pas de ZWJ dans les champs sensibles
Linting CI: scans de fichiers pour U+200D et rapports explicites

Checklist rapide

Règles explicites d'interdiction du ZWJ dans emails/URLs/IDs
Scans automatiques de U+200D dans la CI et les hooks
Éditeur configuré pour afficher les caractères invisibles/format
Fonctions utilitaires strip_zwj() dans vos libs
Tests garantissant l'absence de ZWJ dans les flux critiques
Documentation interne sur ZWJ/ZWNJ, emojis et encodage

Conclusion

Le ZWJ est minuscule mais déterminant: il façonne le rendu et influence vos traitements.

Adoptez une détection systématique, clarifiez les contextes où il est autorisé et appliquez un nettoyage ciblé: vous éviterez la majorité des bugs liés aux séquences invisibles.

Détectez les ZWJ maintenant

Utilisez notre outil pour identifier et nettoyer les ZWJ (U+200D) dans vos textes.

Analyser mon texte pour le ZWJ