CharacterEscapes: la perle cachée de Jackson
A Kestra, la plateforme d’orchestration de données pour laquelle je travaille, on a eu une issue (#10326) ouverte d’un utilisateur qui rapportait un problème avec la base PostgreSQL et le caractère Unicode \u0000
. Une tâche de workflow qui retournait ce caractère en outpout plantait.
Après investigation, PostgreSQL refuse de stocker une entrée JSONB contenant ce caractère, car il n’a pas de représentation textuelle. En effet, c’est le caractère null en Unicode, celui-ci n’est pas autorisé en JSON qui représente null par la chaîne de caractères
null
.
Entre en action une fonctionnalité cachée de Jackson, que nous utilisons pour notre couche de sérialisation JSON,
CharacterEscapes
.
Cette classe permet de configurer la façon donc Jackson va échapper (
escape en anglais) les caractères spéciaux et nous permettre d’échapper le caractère Unicode \u0000
et éviter de planter une tâche de workflow qui le retournerait en output.
public class SafeguardCharacterEscapes extends CharacterEscapes { // <1> private static final SerializableString NULL = new SerializedString("null"); // <2> private final int[] asciiEscapes; SafeguardCharacterEscapes() { // <3> Start with the standard JSON escapes asciiEscapes = CharacterEscapes.standardAsciiEscapesForJSON(); // <4> And then specify that the null character should be escaped asciiEscapes[0] = CharacterEscapes.ESCAPE_CUSTOM; } @Override public int[] getEscapeCodesForAscii() { return asciiEscapes; } @Override public SerializableString getEscapeSequence(int ch) { if (ch == 0) { return NULL; // <5> } return null; // <6> } }
- On créé notre propre implémentation de
CharacterEscapes
, elle sera utilisée lors de la configuration de l’ObjectMapper
Jackson. Voir ci-dessous. - On définit une String pré-calculée qui sera utilisé lors de la sérialisation du caractère, elle retournera la chaîne de caractère
null
. - On pré-calcul les codes d’échappements pour la plage de caractères ASCII, comme ces caractères sont les plus utilisés, ils ont un chemin rapide via un tableau de correspondance qu’on pré-calcul ici en récupérant les codes d’échappement standard.
- On remplace le code d’échappement standard du caractère ASCII
, qui correspond au caractère unicode
\u0000
, car les 128 premiers caractères unicodes coincident avec la place ASCII, par une séquence d’échappement personnalisée (custom en anglais). Les séquences personnalisées sont définies dynamiquement par la méthodegetEscapeSequence(int)
. - On retourne notre String pré-calculée comme séquence personnalisée pour le caractère qui correspond au code point 0.
- On retourne
null
pour toutes les autres indiquant qu’il n’y a pas d’autre séquence d’échappement personnalisée.
Dernière étape, on va configurer l’
ObjectMapper
de Jackson pour utiliser notre CharacterEscapes
personnalisé:
ObjectMapper objectMapper = new ObjectMapper(); objectMapper.getFactory().setCharacterEscapes(new SafeguardCharacterEscapes());
Et voilà, avec ça, plus de tâche qui plantent ! Merci Jackson et sa perle cachée
CharacterEscapes
!
Pour finir, après discussion, nous avons décidé de ne pas intégrer ce changement dans Kestra. Si le caractère n’est pas supporté par PostgreSQL, il y a une bonne raison. Un output de tâche doit être représentable en JSON et
\0000
n’est pas un JSON valide.
Mais même si nous n’avons pas intégré ce changement, je trouvais la fonctionnalité suffisamment intéressante pour vous la faire découvrir ;).