Exclure les champs de la sérialisation dans Gson

Exclure des champs de la sérialisation dans Gson

1. Vue d'ensemble

Dans ce court didacticiel, nous allons explorer les options disponibles pour exclure un ou plusieurs champs d'une classe Java et de ses sous-classes de la sérialisation Gson.

2. La configuration initiale

Commençons par définir nos classes:

@Data
@AllArgsConstructor
public class MyClass {
    private long id;
    private String name;
    private String other;
    private MySubClass subclass;
}

@Data
@AllArgsConstructor
public class MySubClass {
    private long id;
    private String description;
    private String otherVerboseInfo;
}

Nous les avons annotés avecLombok pour plus de commodité (sucre syntaxique pour les getters, les setters, les constructeurs…).

Remplissons-les maintenant:

MySubClass subclass = new MySubClass(42L, "the answer", "Verbose field not to serialize")
MyClass source = new MyClass(1L, "foo", "bar", subclass);

Notre objectif est d'empêcher la sérialisation des champsMyClass.other etMySubClass.otherVerboseInfo.

Le résultat attendu est le suivant:

{
  "id":1,
  "name":"foo",
  "subclass":{
    "id":42,
    "description":"the answer"
  }
}

En Java:

String expectedResult = "{\"id\":1,\"name\":\"foo\",\"subclass\":{\"id\":42,\"description\":\"the answer\"}}";

3. Modificateur transitoire

On peut marquer un champ avec le modificateurtransient:

public class MyClass {
    private long id;
    private String name;
    private transient String other;
    private MySubClass subclass;
}

public class MySubClass {
    private long id;
    private String description;
    private transient String otherVerboseInfo;
}

Le sérialiseur Gson ignorera tous les champs déclarés transitoires:

String jsonString = new Gson().toJson(source);
assertEquals(expectedResult, jsonString);

Bien que cela soit très rapide, cela présente également un inconvénient majeur:every serialization tool will take transient into account, pas seulement Gson.

Transient est le moyen Java d'exclure de la sérialisation, alors notre champ sera également filtré par la sérialisation deSerializable et par chaque outil de bibliothèque ou framework gérant nos objets.

De plus, le mot clétransient fonctionne toujours à la fois pour la sérialisation et la désérialisation, ce qui peut être limitatif en fonction des cas d'utilisation.

4. Annotation@Expose

L'annotation de Gsoncom.google.gson.annotations @Expose fonctionne dans l'autre sens.

Nous pouvons l'utiliser pour déclarer les champs à sérialiser et ignorer les autres:

public class MyClass {
    @Expose
    private long id;
    @Expose
    private String name;
    private String other;
    @Expose
    private MySubClass subclass;
}

public class MySubClass {
    @Expose
    private long id;
    @Expose
    private String description;
    private String otherVerboseInfo;
}

Pour cela, nous devons instancier Gson avec un GsonBuilder:

Gson gson = new GsonBuilder()
  .excludeFieldsWithoutExposeAnnotation()
  .create();
String jsonString = gson.toJson(source);
assertEquals(expectedResult, jsonString);

Cette fois, nous pouvons contrôler au niveau du champ si le filtrage doit avoir lieu pour la sérialisation, la désérialisation ou les deux (par défaut).

Voyons comment empêcher la sérialisation deMyClass.other, mais en autorisant son remplissage lors d'une désérialisation à partir de JSON:

@Expose(serialize = false, deserialize = true)
private String other;

Bien que ce soit le moyen le plus simple fourni par Gson, et qu'il n'affecte pas les autres bibliothèques, cela pourrait impliquer une redondance dans le code. Si nous avons une classe avec une centaine de champs et que nous ne voulons exclure qu'un seul champ, nous devons écrire quatre-vingt-dix-neuf annotations, ce qui est excessif.

5. ExclusionStrategy

Une solution hautement personnalisable est l'utilisation d'uncom.google.gson.*ExclusionStrategy*.

Cela nous permet de définir (en externe ou avec une classe interne anonyme) une stratégie pour indiquer à GsonBuilder s'il faut sérialiser les champs (et / ou les classes) avec des critères personnalisés.

Gson gson = new GsonBuilder()
  .addSerializationExclusionStrategy(strategy)
  .create();
String jsonString = gson.toJson(source);

assertEquals(expectedResult, jsonString);

Voyons quelques exemples de stratégies intelligentes à utiliser.

5.1. Avec les noms de classes et de champs

Bien sûr, nous pouvons également coder en dur un ou plusieurs noms de champs / classes:

ExclusionStrategy strategy = new ExclusionStrategy() {
    @Override
    public boolean shouldSkipField(FieldAttributes field) {
        if (field.getDeclaringClass() == MyClass.class && field.getName().equals("other")) {
            return true;
        }
        if (field.getDeclaringClass() == MySubClass.class && field.getName().equals("otherVerboseInfo")) {
            return true;
        }
        return false;
    }

    @Override
    public boolean shouldSkipClass(Class clazz) {
        return false;
    }
};

C’est rapide et droit au but, mais pas très réutilisable et également sujet à des erreurs au cas où nous renommerions nos attributs.

5.2. Avec des critères commerciaux

Comme nous devons simplement renvoyer un booléen, nous pouvons implémenter chaque logique métier que nous aimons dans cette méthode.

Dans l'exemple suivant, nous identifierons chaque champ commençant par "autre" comme des champs qui ne doivent pas être sérialisés, quelle que soit la classe à laquelle ils appartiennent:

ExclusionStrategy strategy = new ExclusionStrategy() {
    @Override
    public boolean shouldSkipClass(Class clazz) {
        return false;
    }

    @Override
    public boolean shouldSkipField(FieldAttributes field) {
        return field.getName().startsWith("other");
    }
};

5.3. Avec une annotation personnalisée

Une autre approche intelligente consiste à créer une annotation personnalisée:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Exclude {}

On peut alors exploiterExclusionStrategy afin de le faire fonctionner exactement comme avec l'annotation@Expose, mais inversement:

public class MyClass {
    private long id;
    private String name;
    @Exclude
    private String other;
    private MySubClass subclass;
}

public class MySubClass {
    private long id;
    private String description;
    @Exclude
    private String otherVerboseInfo;
}

Et voici la stratégie:

ExclusionStrategy strategy = new ExclusionStrategy() {
    @Override
    public boolean shouldSkipClass(Class clazz) {
        return false;
    }

    @Override
    public boolean shouldSkipField(FieldAttributes field) {
        return field.getAnnotation(Exclude.class) != null;
    }
};

This StackOverflow answer a d'abord décrit cette technique.

Cela nous permet d'écrire l'annotation et la stratégie une fois, et d'annoter dynamiquement nos champs sans autre modification.

5.4. Étendre la stratégie d'exclusion à la désérialisation

Quelle que soit la stratégie que nous utiliserons, nous pouvons toujours contrôler où elle doit être appliquée.

Seulement pendant la sérialisation:

Gson gson = new GsonBuilder().addSerializationExclusionStrategy(strategy)

Seulement pendant la désérialisation:

Gson gson = new GsonBuilder().addDeserializationExclusionStrategy(strategy)

Toujours:

Gson gson = new GsonBuilder().setExclusionStrategies(strategy);

6. Conclusion

Nous avons vu différentes manières d'exclure des champs d'une classe et de ses sous-classes lors de la sérialisation Gson.

Nous avons également exploré les principaux avantages et pièges de chaque solution.

Comme toujours, le code source complet est disponibleover on Github.