449 votes

Gson : Comment exclure des champs spécifiques de la sérialisation sans annotation ?

J'essaie d'apprendre Gson et j'ai du mal avec l'exclusion de champs. Voici mes classes

public class Student {    
  private Long                id;
  private String              firstName        = "Philip";
  private String              middleName       = "J.";
  private String              initials         = "P.F";
  private String              lastName         = "Fry";
  private Country             country;
  private Country             countryOfBirth;
}

public class Country {    
  private Long                id;
  private String              name;
  private Object              other;
}

Je peux utiliser le GsonBuilder et ajouter une ExclusionStrategy pour un nom de champ, par exemple firstName ou country mais je ne parviens pas à exclure les propriétés de certains champs comme country.name .

Utilisation de la méthode public boolean shouldSkipField(FieldAttributes fa) si FieldAttributes ne contient pas suffisamment d'informations pour faire correspondre le champ avec un filtre tel que country.name .

P.S. : Je veux éviter les annotations car je veux améliorer cela et utiliser RegEx pour filtrer les champs.

Modifier : J'essaie de voir s'il est possible d'émuler le comportement de Plugin Struts2 JSON

en utilisant Gson

<interceptor-ref name="json">
  <param name="enableSMD">true</param>
  <param name="excludeProperties">
    login.password,
    studentList.*\.sin
  </param>
</interceptor-ref>

Edit : J'ai rouvert la question avec l'ajout suivant :

J'ai ajouté un deuxième champ du même type pour clarifier davantage ce problème. En gros, je veux exclure country.name mais pas countrOfBirth.name . Je ne veux pas non plus exclure le Pays comme type. Les types sont donc les mêmes, c'est l'emplacement réel dans le graphe d'objets que je veux localiser et exclure.

1 votes

Depuis la version 2.2, je ne peux toujours pas spécifier un chemin d'accès au champ à exclure. flexjson.sourceforge.net semble être une bonne alternative.

0 votes

Jetez un coup d'œil sur ma réponse à une question assez similaire. Elle repose sur la création d'un JsonSerializer pour un certain type - Country dans votre cas - pour lequel on applique alors un ExclusionStrategy qui décide des champs à sérialiser.

0 votes

666voto

Chris Seline Points 876

Pour tous les champs que vous ne voulez pas sérialiser en général, vous devez utiliser le modificateur "transient", et cela s'applique également aux sérialiseurs json (du moins pour quelques-uns que j'ai utilisés, dont gson).

Si vous ne voulez pas que le nom apparaisse dans le json sérialisé, donnez-lui un mot-clé transitoire, par exemple :

private transient String name;

Plus de détails dans la documentation de Gson

7 votes

C'est presque la même chose qu'une annotation d'exclusion, car elle s'applique à toutes les instances de cette classe. Je voulais une exclusion dynamique au moment de l'exécution. Dans certains cas, je veux que certains champs soient exclus afin de fournir une réponse plus légère/restrictive et dans d'autres, je veux que l'objet complet soit sérialisé.

37 votes

Une chose à noter est que le transitoire affecte à la fois la sérialisation et la désérialisation. Il émettra également la valeur qui a été sérialisée dans l'objet, même si elle est présente dans le JSON.

0 votes

Belle façon d'exclure. Elle m'aide à générer des configurations JSON à partir de POJOs à utiliser avec TickTick.

327voto

JayPea Points 2059

Nishant a fourni une bonne solution, mais il existe un moyen plus simple. Il suffit de marquer les champs souhaités avec l'annotation @Expose, comme :

@Expose private Long id;

Laissez de côté tous les champs que vous ne souhaitez pas sérialiser. Ensuite, créez simplement votre objet Gson de cette façon :

Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();

108 votes

Est-il possible d'avoir quelque chose comme "notExpose" et de ne les ignorer que dans le cas où tous les champs sauf un doivent être sérialisés et que l'ajout d'annotations à tous ces champs est redondant ?

3 votes

@DaSh J'ai récemment eu un tel scénario. Il était très facile d'écrire une ExclusionStrategy personnalisée qui faisait exactement cela. Voir la réponse de Nishant. Le seul problème était d'inclure un tas de classes de conteneurs et de jouer avec skipclass vs skipfield (les champs peuvent être des classes...).

2 votes

@DaSh Ma réponse ci-dessous fait exactement cela.

248voto

Nishant Points 22758

Donc, vous voulez exclure firstName et country.name . Voici ce que votre ExclusionStrategy devrait ressembler à

    public class TestExclStrat implements ExclusionStrategy {

        public boolean shouldSkipClass(Class<?> arg0) {
            return false;
        }

        public boolean shouldSkipField(FieldAttributes f) {

            return (f.getDeclaringClass() == Student.class && f.getName().equals("firstName"))||
            (f.getDeclaringClass() == Country.class && f.getName().equals("name"));
        }

    }

Si vous voyez de près il retourne true pour Student.firstName et Country.name ce qui est ce que vous voulez exclure.

Vous devez appliquer cette ExclusionStrategy comme ça,

    Gson gson = new GsonBuilder()
        .setExclusionStrategies(new TestExclStrat())
        //.serializeNulls() <-- uncomment to serialize NULL fields as well
        .create();
    Student src = new Student();
    String json = gson.toJson(src);
    System.out.println(json);

Cela revient :

{ "middleName": "J.", "initials": "P.F", "lastName": "Fry", "country": { "id": 91}}

Je suppose que l'objet pays est initialisé avec id = 91L dans la classe des élèves.


Vous risquez de devenir fantaisiste. Par exemple, vous ne voulez pas sérialiser un champ dont le nom contient la chaîne "name". Faites ceci :

public boolean shouldSkipField(FieldAttributes f) {
    return f.getName().toLowerCase().contains("name"); 
}

Cela reviendra :

{ "initials": "P.F", "country": { "id": 91 }}

EDIT : J'ai ajouté plus d'informations comme demandé.

Ce site ExclusionStrategy fera l'affaire, mais vous devez passer le "Fully Qualified Field Name". Voir ci-dessous :

    public class TestExclStrat implements ExclusionStrategy {

        private Class<?> c;
        private String fieldName;
        public TestExclStrat(String fqfn) throws SecurityException, NoSuchFieldException, ClassNotFoundException
        {
            this.c = Class.forName(fqfn.substring(0, fqfn.lastIndexOf(".")));
            this.fieldName = fqfn.substring(fqfn.lastIndexOf(".")+1);
        }
        public boolean shouldSkipClass(Class<?> arg0) {
            return false;
        }

        public boolean shouldSkipField(FieldAttributes f) {

            return (f.getDeclaringClass() == c && f.getName().equals(fieldName));
        }

    }

Voici comment nous pouvons l'utiliser de manière générique.

    Gson gson = new GsonBuilder()
        .setExclusionStrategies(new TestExclStrat("in.naishe.test.Country.name"))
        //.serializeNulls()
        .create();
    Student src = new Student();
    String json = gson.toJson(src);
    System.out.println(json); 

Il revient :

{ "firstName": "Philip" , "middleName": "J.", "initials": "P.F", "lastName": "Fry", "country": { "id": 91 }}

0 votes

Merci pour votre réponse. Ce que je veux, c'est créer une ExclusionStrategy qui peut prendre une chaîne de caractères telle que country.name et n'exclure que le champ name lors de la sérialisation du champ country . Il doit être suffisamment générique pour s'appliquer à toutes les classes qui ont une propriété nommée pays de la classe Pays. Je ne veux pas créer une ExclusionStrategy pour chaque classe.

0 votes

@Liviu T. J'ai mis à jour la réponse. Il s'agit d'une approche générique. Vous pouvez être encore plus créatif, mais je l'ai gardé élémentaire.

0 votes

Ty pour la mise à jour. Ce que j'essaie de voir, c'est s'il est possible de savoir où je me trouve dans le graphe d'objets lorsque la méthode est appelée, afin que je puisse exclure certains champs du pays mais pas le pays de naissance (par exemple), donc la même classe mais des propriétés différentes. J'ai modifié ma question pour clarifier ce que j'essaie d'obtenir.

87voto

Derek Points 507

J'ai rencontré ce problème, dans lequel j'avais un petit nombre de champs que je voulais exclure uniquement de la sérialisation. J'ai donc mis au point une solution assez simple qui utilise la méthode de Gson @Expose avec des stratégies d'exclusion personnalisées.

La seule façon intégrée d'utiliser @Expose c'est en fixant GsonBuilder.excludeFieldsWithoutExposeAnnotation() mais, comme son nom l'indique, les champs qui n'ont pas de nom de domaine explicite sont considérés comme des champs à part entière. @Expose sont ignorés. Comme je n'avais que quelques champs à exclure, j'ai trouvé la perspective d'ajouter l'annotation à chaque champ très encombrante.

Je voulais effectivement l'inverse, dans lequel tout était inclus sauf si j'utilisais explicitement @Expose pour l'exclure. J'ai utilisé les stratégies d'exclusion suivantes pour y parvenir :

new GsonBuilder()
        .addSerializationExclusionStrategy(new ExclusionStrategy() {
            @Override
            public boolean shouldSkipField(FieldAttributes fieldAttributes) {
                final Expose expose = fieldAttributes.getAnnotation(Expose.class);
                return expose != null && !expose.serialize();
            }

            @Override
            public boolean shouldSkipClass(Class<?> aClass) {
                return false;
            }
        })
        .addDeserializationExclusionStrategy(new ExclusionStrategy() {
            @Override
            public boolean shouldSkipField(FieldAttributes fieldAttributes) {
                final Expose expose = fieldAttributes.getAnnotation(Expose.class);
                return expose != null && !expose.deserialize();
            }

            @Override
            public boolean shouldSkipClass(Class<?> aClass) {
                return false;
            }
        })
        .create();

Maintenant, je peux facilement exclure quelques champs avec @Expose(serialize = false) ou @Expose(deserialize = false) (notez que la valeur par défaut pour les deux annotations @Expose Les attributs sont true ). Vous pouvez bien sûr utiliser @Expose(serialize = false, deserialize = false) mais cela se fait de manière plus concise en déclarant le champ transient à la place (qui prend toujours effet avec ces stratégies d'exclusion personnalisées).

0 votes

Pour des raisons d'efficacité, je pense qu'il est préférable d'utiliser @Expose(serialize = false, deserialize = false) plutôt que transient.

1 votes

@paiego Pouvez-vous développer ce point ? Je n'utilise plus Gson depuis de nombreuses années, et je ne comprends pas pourquoi l'annotation est plus efficace que le marquage transitoire.

0 votes

Ahh, j'ai fait une erreur, merci de l'avoir remarqué. J'ai confondu volatile et transitoire. (Il n'y a pas de cache et donc pas de problème de cohérence de cache avec volatile, mais c'est moins performant). Quoi qu'il en soit, votre code fonctionne parfaitement !

17voto

Domenic D. Points 1265

J'ai imaginé une fabrique de classes pour prendre en charge cette fonctionnalité. Passez n'importe quelle combinaison de champs ou de classes que vous voulez exclure.

public class GsonFactory {

    public static Gson build(final List<String> fieldExclusions, final List<Class<?>> classExclusions) {
        GsonBuilder b = new GsonBuilder();
        b.addSerializationExclusionStrategy(new ExclusionStrategy() {
            @Override
            public boolean shouldSkipField(FieldAttributes f) {
                return fieldExclusions == null ? false : fieldExclusions.contains(f.getName());
            }

            @Override
            public boolean shouldSkipClass(Class<?> clazz) {
                return classExclusions == null ? false : classExclusions.contains(clazz);
            }
        });
        return b.create();

    }
}

Pour l'utiliser, créez deux listes (chacune est facultative), et créez votre objet GSON :

static {
 List<String> fieldExclusions = new ArrayList<String>();
 fieldExclusions.add("id");
 fieldExclusions.add("provider");
 fieldExclusions.add("products");

 List<Class<?>> classExclusions = new ArrayList<Class<?>>();
 classExclusions.add(Product.class);
 GSON = GsonFactory.build(null, classExclusions);
}

private static final Gson GSON;

public String getSomeJson(){
    List<Provider> list = getEntitiesFromDatabase();
    return GSON.toJson(list);
}

0 votes

Bien sûr, il est possible de modifier cette méthode pour examiner le nom pleinement qualifié de l'attribut et l'exclure en cas de correspondance...

0 votes

Je fais l'exemple ci-dessous. Cela ne fonctionne pas. Pls suggest private static final Gson GSON ; static { List<Stringing> fieldExclusions = new ArrayList<String>() ; fieldExclusions.add("id") ; GSON = GsonFactory. build(fieldExclusions, null) ; } private static String getSomeJson() { String jsonStr = "[{\"id\":111,\"name\":\"praveen\",\"age\":16},{\"id\" : 222,\"name\":\"prashant\",\"age\":20}]" ; return jsonStr ; } public static void main(String[] args) { String jsonStr = getSomeJson() ; System. out.println(GSON.toJson(jsonStr)) ; }

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X