CharacterEscapes: la perle cachée de Jackson

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>
    }
}
  1. On créé notre propre implémentation de CharacterEscapes, elle sera utilisée lors de la configuration de l’ ObjectMapper Jackson. Voir ci-dessous.
  2. 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.
  3. 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.
  4. 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éthode getEscapeSequence(int).
  5. On retourne notre String pré-calculée comme séquence personnalisée pour le caractère qui correspond au code point 0.
  6. 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 ;).

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur la façon dont les données de vos commentaires sont traitées.