web-dev-qa-db-fra.com

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

J'essaie d'apprendre Gson et je lutte contre l'exclusion sur le terrain. Voici mes cours

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 GsonBuilder et ajouter une ExclusionStrategy pour un nom de champ tel que firstName ou country, mais je ne parviens pas à exclure les propriétés de certains champs comme country.name.

En utilisant la méthode public boolean shouldSkipField(FieldAttributes fa), FieldAttributes ne contient pas assez d'informations pour faire correspondre le champ à un filtre comme country.name.

J'apprécierais toute aide avec une solution à ce problème.

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

Je vous remercie

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

en utilisant Gson

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

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

J'ai ajouté un deuxième champ du même type pour clarifier ce problème. En gros, je veux exclure country.name mais pas countrOfBirth.name. De plus, je ne veux pas exclure Country en tant que type ... Donc, les types sont identiques, c'est la position réelle dans le graphe d'objets que je souhaite identifier et exclure.

386
Liviu T.

Tous les champs que vous ne voulez pas sérialiser en général, vous devez utiliser le modificateur "transitoire", et cela s'applique également aux sérialiseurs json (du moins c'est le cas pour certains que j'ai utilisés, y compris gson).

Si vous ne voulez pas que name 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 Gson

585
Chris Seline

Nishant a fourni une bonne solution, mais il existe un moyen plus simple. Marquez simplement les champs souhaités avec l'annotation @Expose, tels que:

@Expose private Long id;

Laissez 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();
299
JayPea

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

    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 que vous voulez exclure.

Vous devez appliquer cette ExclusionStrategy comme ceci,

    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 retourne:

{"middleName": "J.", "initiales": "P.F", "lastName": "Fry", "pays": {"id": 91}}

Je suppose que l'objet pays est initialisé avec id = 91L dans la classe de l'étudiant.


Vous pouvez avoir envie. Par exemple, vous ne souhaitez pas sérialiser les champs contenant la chaîne "name" dans son nom. Faire ceci:

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

Cela retournera:

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

EDIT: Ajout de plus d'informations à la demande.

Cette ExclusionStrategy fera l'affaire, mais vous devez passer "Nom de champ entièrement qualifié". 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 retourne:

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

Après avoir lu toutes les réponses disponibles, j'ai découvert que le plus flexible, dans mon cas, consistait à utiliser l'annotation @Exclude personnalisée. J'ai donc mis en œuvre une stratégie simple pour cela (je ne voulais pas marquer tous les champs en utilisant @Expose ni je voulais utiliser transient qui entrait en conflit avec la sérialisation app Serializable):

Annotation:

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

Stratégie:

public class AnnotationExclusionStrategy implements ExclusionStrategy {

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

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

Usage:

new GsonBuilder().setExclusionStrategies(new AnnotationExclusionStrategy()).create();
191
pkk

Je me suis heurté à ce problème, dans lequel je ne voulais exclure que de la sérialisation un petit nombre de champs. J'ai donc développé une solution assez simple qui utilise l'annotation @Expose de Gson avec des stratégies d'exclusion personnalisées.

La seule manière intégrée d'utiliser @Expose consiste à définir GsonBuilder.excludeFieldsWithoutExposeAnnotation(), mais comme son nom l'indique, les champs sans @Expose explicite sont ignorés. Comme je n’avais que quelques champs à exclure, j’ai trouvé très difficile d’ajouter une annotation à chaque champ.

J'ai effectivement voulu 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 des annotations @Expose(serialize = false) ou @Expose(deserialize = false) (notez que la valeur par défaut pour les deux attributs @Expose est true). Vous pouvez bien sûr utiliser @Expose(serialize = false, deserialize = false), mais cela est accompli de manière plus concise en déclarant le champ transient à la place (ce qui prend toujours effet avec ces stratégies d'exclusion personnalisées).

75
Derek Shockey

Je suis venu avec une usine de classe pour prendre en charge cette fonctionnalité. Transmettez n'importe quelle combinaison de champs ou de classes à 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 optionnelle) 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);
}
16
Domenic D.

Vous pouvez explorer l'arbre json avec gson.

Essayez quelque chose comme ça: 

gson.toJsonTree(student).getAsJsonObject()
.get("country").getAsJsonObject().remove("name");

Vous pouvez également ajouter des propriétés:

gson.toJsonTree(student).getAsJsonObject().addProperty("isGoodStudent", false);

Testé avec gson 2.2.4.

15
Klaudia Rzewnicka

J'ai résolu ce problème avec des annotations personnalisées . C'est ma classe d'annotation "SkipSerialisation": 

@Target (ElementType.FIELD)
public @interface SkipSerialisation {

}

et voici mon GsonBuilder:

gsonBuilder.addSerializationExclusionStrategy(new ExclusionStrategy() {

  @Override public boolean shouldSkipField (FieldAttributes f) {

    return f.getAnnotation(SkipSerialisation.class) != null;

  }

  @Override public boolean shouldSkipClass (Class<?> clazz) {

    return false;
  }
});

Exemple :

public class User implements Serializable {

  public String firstName;

  public String lastName;

  @SkipSerialisation
  public String email;
}
13
Reyske

Ou peut dire ce que les champs n'exposeront pas avec:

Gson gson = gsonBuilder.excludeFieldsWithModifiers(Modifier.TRANSIENT).create();

sur votre classe sur l'attribut:

private **transient** boolean nameAttribute;
8
lucasddaniel

J’ai utilisé cette stratégie: J’ai exclu tous les champs qui sont marqués not avec l’annotation @SerializedName, c’est-à-dire:

public class Dummy {

    @SerializedName("VisibleValue")
    final String visibleValue;
    final String hiddenValue;

    public Dummy(String visibleValue, String hiddenValue) {
        this.visibleValue = visibleValue;
        this.hiddenValue = hiddenValue;
    }
}


public class SerializedNameOnlyStrategy implements ExclusionStrategy {

    @Override
    public boolean shouldSkipField(FieldAttributes f) {
        return f.getAnnotation(SerializedName.class) == null;
    }

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


Gson gson = new GsonBuilder()
                .setExclusionStrategies(new SerializedNameOnlyStrategy())
                .create();

Dummy dummy = new Dummy("I will see this","I will not see this");
String json = gson.toJson(dummy);

Il retourne

{"VisibleValue": "Je vais voir ça"}

6
MadMurdok

Une autre approche (particulièrement utile si vous devez prendre une décision d'exclure un champ au moment de l'exécution) consiste à enregistrer un TypeAdapter avec votre instance gson. Exemple ci-dessous:

Gson gson = new GsonBuilder()
.registerTypeAdapter(BloodPressurePost.class, new BloodPressurePostSerializer())

Dans le cas ci-dessous, le serveur s'attend à l'une des deux valeurs, mais comme elles sont toutes les deux ints, gson les sérialise. Mon objectif était d’omettre toute valeur nulle (ou inférieure) au json publié sur le serveur.

public class BloodPressurePostSerializer implements JsonSerializer<BloodPressurePost> {

    @Override
    public JsonElement serialize(BloodPressurePost src, Type typeOfSrc, JsonSerializationContext context) {
        final JsonObject jsonObject = new JsonObject();

        if (src.systolic > 0) {
            jsonObject.addProperty("systolic", src.systolic);
        }

        if (src.diastolic > 0) {
            jsonObject.addProperty("diastolic", src.diastolic);
        }

        jsonObject.addProperty("units", src.units);

        return jsonObject;
    }
}
6
Damian

La notation @Transient de Kotlin fait aussi apparemment l'affaire.

data class Json(
    @field:SerializedName("serialized_field_1") val field1: String,
    @field:SerializedName("serialized_field_2") val field2: String,
    @Transient val field3: String
)

Sortie:

{"serialized_field_1":"VALUE1","serialized_field_2":"VALUE2"}
1
lookashc

Je travaille juste en mettant l'annotation @Expose, voici ma version que j'utilise

compile 'com.squareup.retrofit2:retrofit:2.0.2'
compile 'com.squareup.retrofit2:converter-gson:2.0.2'

Dans la classe Model:

@Expose
int number;

public class AdapterRestApi {

Dans la classe Adapter:

public EndPointsApi connectRestApi() {
    OkHttpClient client = new OkHttpClient.Builder()
            .connectTimeout(90000, TimeUnit.SECONDS)
            .readTimeout(90000,TimeUnit.SECONDS).build();

    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(ConstantRestApi.ROOT_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .client(client)
            .build();

    return retrofit.create  (EndPointsApi.class);
}
1
devitoper

C'est ce que j'utilise toujours:

Le comportement par défaut implémenté dans Gson est que les champs d'objet NULL sont ignorés.

Signifie que l'objet Gson ne sérialise pas les champs avec des valeurs NULL au format JSON. Si un champ dans un objet Java est null, Gson l'exclut.

Vous pouvez utiliser cette fonction pour convertir un objet en null ou bien défini par votre propre

     /**
   * convert object to json
   */
  public String toJson(Object obj) {
    // Convert emtpy string and objects to null so we don't serialze them
    setEmtpyStringsAndObjectsToNull(obj);
    return gson.toJson(obj);
  }

  /**
   * Sets all empty strings and objects (all fields null) including sets to null.
   *
   * @param obj any object
   */
  public void setEmtpyStringsAndObjectsToNull(Object obj) {
    for (Field field : obj.getClass().getDeclaredFields()) {
      field.setAccessible(true);
      try {
        Object fieldObj = field.get(obj);
        if (fieldObj != null) {
          Class fieldType = field.getType();
          if (fieldType.isAssignableFrom(String.class)) {
            if(fieldObj.equals("")) {
              field.set(obj, null);
            }
          } else if (fieldType.isAssignableFrom(Set.class)) {
            for (Object item : (Set) fieldObj) {
              setEmtpyStringsAndObjectsToNull(item);
            }
            boolean setFielToNull = true;
            for (Object item : (Set) field.get(obj)) {
              if(item != null) {
                setFielToNull = false;
                break;
              }
            }
            if(setFielToNull) {
              setFieldToNull(obj, field);
            }
          } else if (!isPrimitiveOrWrapper(fieldType)) {
            setEmtpyStringsAndObjectsToNull(fieldObj);
            boolean setFielToNull = true;
            for (Field f : fieldObj.getClass().getDeclaredFields()) {
              f.setAccessible(true);
              if(f.get(fieldObj) != null) {
                setFielToNull = false;
                break;
              }
            }
            if(setFielToNull) {
              setFieldToNull(obj, field);
            }
          }
        }
      } catch (IllegalAccessException e) {
        System.err.println("Error while setting empty string or object to null: " + e.getMessage());
      }
    }
  }

  private void setFieldToNull(Object obj, Field field) throws IllegalAccessException {
    if(!Modifier.isFinal(field.getModifiers())) {
      field.set(obj, null);
    }
  }

  private boolean isPrimitiveOrWrapper(Class fieldType)  {
    return fieldType.isPrimitive()
        || fieldType.isAssignableFrom(Integer.class)
        || fieldType.isAssignableFrom(Boolean.class)
        || fieldType.isAssignableFrom(Byte.class)
        || fieldType.isAssignableFrom(Character.class)
        || fieldType.isAssignableFrom(Float.class)
        || fieldType.isAssignableFrom(Long.class)
        || fieldType.isAssignableFrom(Double.class)
        || fieldType.isAssignableFrom(Short.class);
  }
0
Julian Solarte

J'ai la version Kotlin 

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FIELD)
internal annotation class JsonSkip

class SkipFieldsStrategy : ExclusionStrategy {

    override fun shouldSkipClass(clazz: Class<*>): Boolean {
        return false
    }

    override fun shouldSkipField(f: FieldAttributes): Boolean {
        return f.getAnnotation(JsonSkip::class.Java) != null
    }
}

et comment vous pouvez ajouter ceci à Retrofit GSONConverterFactory: 

val gson = GsonBuilder()
                .setExclusionStrategies(SkipFieldsStrategy())
                //.serializeNulls()
                //.setDateFormat(DateFormat.LONG)
                //.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
                //.setPrettyPrinting()
                //.registerTypeAdapter(Id.class, IdTypeAdapter())
                .create()
        return GsonConverterFactory.create(gson)
0
Michał Ziobro