Gson: cómo excluir campos específicos de la serialización sin anotaciones

413

Estoy tratando de aprender Gson y estoy luchando con la exclusión de campo. Aqui estan mis clases

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;
}

Puedo usar GsonBuilder y agregar una ExclusionStrategy para un nombre de campo como firstNameo countrypero parece que no logro excluir propiedades de ciertos campos como country.name.

Usando el método public boolean shouldSkipField(FieldAttributes fa), FieldAttributes no contiene suficiente información para hacer coincidir el campo con un filtro como country.name.

PD: Quiero evitar anotaciones ya que quiero mejorar esto y usar RegEx para filtrar los campos.

Editar : estoy tratando de ver si es posible emular el comportamiento del complemento JSON Struts2

usando Gson

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

Editar: reabrí la pregunta con la siguiente adición:

Agregué un segundo campo con el mismo tipo para aclarar aún más este problema. Básicamente quiero excluir country.namepero no countrOfBirth.name. Tampoco quiero excluir País como tipo. Entonces, los tipos son los mismos, es el lugar real en el gráfico del objeto que quiero señalar y excluir.

Liviu T.
fuente
1
Aún a partir de la versión 2.2, todavía no puedo especificar una ruta al campo para excluir. flexjson.sourceforge.net se siente como una buena alternativa.
Liviu T.
Echa un vistazo a mi respuesta a una pregunta bastante similar. Se basa en la creación de un tipo personalizado JsonSerializerpara algún tipo, Countryen su caso, para el que luego se aplica y ExclusionStrategyque decide qué campos serializar.
pirho

Respuestas:

625

Cualquier campo que no desee serializar en general, debe usar el modificador "transitorio", y esto también se aplica a los serializadores json (al menos a algunos que he usado, incluido gson).

Si no desea que el nombre aparezca en el json serializado, dele una palabra clave transitoria, por ejemplo:

private transient String name;

Más detalles en la documentación de Gson

Chris Seline
fuente
66
es casi lo mismo que una anotación de exclusión, ya que se aplica a todas las instancias de esa clase. Quería exclusión dinámica en tiempo de ejecución. En algunos casos, quiero excluir algunos campos para proporcionar una respuesta más clara / restringida y en otros quiero serializar el objeto completo
Liviu T.
34
Una cosa a tener en cuenta es que los efectos transitorios tanto la serialización como la deserialización. También emitirá el valor de ser serializado en el objeto, incluso si está presente en el JSON.
Kong
3
El problema con el uso en transientlugar de @Exposees que todavía tiene que simular un POJO en su cliente con todos los campos que podrían entrar. En el caso de una API de back-end que podría compartirse entre proyectos, esto podría ser problemático en caso de que Se agregan campos adicionales. Esencialmente es la lista blanca frente a la lista negra de los campos.
theblang
11
Este enfoque no funcionó para mí, ya que no solo excluyó el campo de la serialización de gson, sino que excluyó el campo de la serialización de la aplicación interna (usando la interfaz serializable).
pkk
8
transitoria evita la SERIALIZACIÓN y la DESERIALIZACIÓN de un campo. Esto no coincide con mis necesidades.
Loenix
318

Nishant proporcionó una buena solución, pero hay una manera más fácil. Simplemente marque los campos deseados con la anotación @Expose, como:

@Expose private Long id;

Omita los campos que no desee serializar. Luego, simplemente cree su objeto Gson de esta manera:

Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
JayPea
fuente
95
¿Es posible tener algo como "notExpose" y solo ignorar aquellos para el caso en que todos menos un campo deben ser serializados y agregar anotaciones a todos ellos es redundante?
Daniil Shevelev
2
@DaSh Recientemente tuve un escenario así. Fue muy fácil escribir una estrategia de exclusión personalizada que hizo exactamente eso. Ver la respuesta de Nishant. El único problema era incluir un montón de clases de contenedor y violín con skipclass vs skipfield (los campos pueden ser clases ...)
keyser
1
@DaSh Mi respuesta a continuación hace exactamente eso.
Derek Shockey
Que gran solución. Me encontraba con un escenario en el que quiero un campo serializado en el disco pero que se ignore al enviarlo a un servidor a través de gson. ¡Perfecto gracias!
Slynk
1
@Danlil Debería poder utilizar @Expose (serializar = falso, deserializar = falso)
Hrk
238

Por lo tanto, desea excluir firstName y country.name. Así es como ExclusionStrategydebería verse

    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 ve de cerca, devuelve truepara Student.firstNamey Country.name, que es lo que desea excluir.

Necesitas aplicar esto ExclusionStrategyasí,

    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);

Esto devuelve:

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

Supongo que el objeto país se inicializa id = 91Len la clase de estudiante.


Puedes ponerte elegante. Por ejemplo, no desea serializar ningún campo que contenga una cadena de "nombre" en su nombre. Hacer esto:

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

Esto devolverá:

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

EDITAR: se agregó más información según lo solicitado.

Esto ExclusionStrategyhará lo necesario, pero debe pasar "Nombre de campo totalmente calificado". Vea abajo:

    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));
        }

    }

Aquí es cómo podemos usarlo genéricamente.

    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); 

Vuelve:

{ "firstName": "Philip" , "middleName": "J.", "initials": "P.F", "lastName": "Fry", "country": { "id": 91 }}
Nishant
fuente
Gracias por tu respuesta. Lo que quiero es crear una country.nameestrategia de exclusión que pueda tomar una cadena como y solo excluir el campo namecuando se serializa el campo country. Debe ser lo suficientemente genérico como para aplicarse a cada clase que tenga una propiedad denominada country de la clase Country. No quiero crear una estrategia de exclusión para cada clase
Liviu T.
@Liviu T. He actualizado la respuesta. Eso toma un enfoque genérico. Puedes ser aún más creativo, pero lo mantuve elemental.
Nishant
Ty para la actualización. Lo que estoy tratando de ver si es posible saber en qué parte del gráfico de objetos estoy cuando el método lo llamó para poder excluir algunos campos del país pero no countryOfBirth (por ejemplo) de la misma clase pero con diferentes propiedades. He editado mi pregunta para aclarar lo que estoy tratando de lograr
Liviu T.
¿Hay alguna manera de excluir campos que tengan valores vacíos?
Yusuf K.
12
Esta respuesta debe marcarse como la respuesta preferida. A diferencia de las otras respuestas que actualmente tienen más votos, esta solución no requiere que modifique la clase de bean. Esta es una gran ventaja. ¿Qué pasaría si alguien más estuviera usando la misma clase de bean y usted marcara un campo que quería que persistiera como "transitorio"?
user64141
221

Después de leer todas las respuestas disponibles, descubrí que lo más flexible, en mi caso, era usar @Excludeanotaciones personalizadas . Entonces, implementé una estrategia simple para esto (no quería marcar todos los campos usando @Exposeni quería usar los transientque estaban en conflicto con la Serializableserialización de la aplicación ):

Anotación:

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

Estrategia:

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;
    }
}

Uso:

new GsonBuilder().setExclusionStrategies(new AnnotationExclusionStrategy()).create();
pkk
fuente
16
Como nota adicional, si desea utilizar su estrategia de exclusión solo para la serialización o solo la deserialización, use: addSerializationExclusionStrategyo en addDeserializationExclusionStrategylugar desetExclusionStrategies
GLee
99
¡Perfecto! La solución transitoria no funciona para mí porque estoy usando Realm para DB y quiero excluir un campo solo de Gson, pero no Realm (que sí lo hace)
Marcio Granzotto
3
Esta debería ser la respuesta aceptada. Para ignorar los campos nulos, simplemente cambie f.getAnnotation(Exclude.class) != nullaf.getAnnotation(Exclude.class) == null
Sharp Edge el
3
Excelente cuando no se puede usar transientdebido a las necesidades de otras bibliotecas. ¡Gracias!
Martin D
1
También es genial para mí porque Android serializa mis clases entre actividades, pero solo quiero que se excluyan cuando uso GSON. Esto permite que mi aplicación siga funcionando de la misma manera hasta que quiera envolverlos para enviarlos a otros.
ThePartyTurtle
81

Me encontré con este problema, en el que tenía una pequeña cantidad de campos que quería excluir solo de la serialización, por lo que desarrollé una solución bastante simple que usa la @Exposeanotación de Gson con estrategias de exclusión personalizadas.

La única forma integrada de usar @Exposees mediante la configuración GsonBuilder.excludeFieldsWithoutExposeAnnotation(), pero como su nombre indica, los campos sin un explícito @Exposese ignoran. Como solo tenía unos pocos campos que quería excluir, encontré la posibilidad de agregar la anotación a cada campo muy engorrosa.

Efectivamente quería lo inverso, en el que todo estaba incluido a menos que explícitamente lo @Exposeexcluyera. Utilicé las siguientes estrategias de exclusión para lograr esto:

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();

Ahora puedo excluir fácilmente unos campos con @Expose(serialize = false)o @Expose(deserialize = false)anotaciones (Nótese que el valor predeterminado para ambos @Exposeatributos es true). Por supuesto @Expose(serialize = false, deserialize = false), puede usarlo , pero eso se logra de manera más concisa al declarar el campo en su transientlugar (que todavía tiene efecto con estas estrategias de exclusión personalizadas).

Derek Shockey
fuente
Por eficiencia, puedo ver un caso para usar @Expose (serialize = false, deserialize = false) en lugar de transitorio.
paiego
1
@paiego ¿Puedes ampliar eso? Hace ya muchos años que no uso Gson, y no entiendo por qué la anotación es más eficiente que marcarla como transitoria.
Derek Shockey el
Ahh, cometí un error, gracias por entender esto. Confundí volátil con transitorio. (por ejemplo, no hay almacenamiento en caché y, por lo tanto, no hay problema de coherencia de caché con volátil, pero es menos eficaz) De todos modos, su código funcionó muy bien
paiego
18

Puedes explorar el árbol json con gson.

Intenta algo como esto:

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

También puede agregar algunas propiedades:

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

Probado con gson 2.2.4.

Klaudia Rzewnicka
fuente
3
Me pregunto si esto es un gran impacto en el rendimiento si desea deshacerse de una propiedad compleja que debe analizarse antes de la eliminación. Pensamientos?
Ben
Definitivamente no es una solución escalable, imagine todo el dolor de cabeza que tendrá que pasar si cambia la estructura de su objeto o agrega / elimina cosas.
codenamezero
16

Se me ocurrió una fábrica de clase para soportar esta funcionalidad. Pase cualquier combinación de campos o clases que desee excluir.

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();

    }
}

Para usar, cree dos listas (cada una es opcional) y cree su objeto 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);
}
Domenic D.
fuente
Por supuesto, esto se puede modificar para ver el nombre completo del atributo y excluirlo en un partido ...
Domenic D.
Estoy haciendo el siguiente ejemplo. Esto no está funcionando. Pls sugiere Gson GSON final estático privado; static {List <String> fieldExclusions = new ArrayList <String> (); fieldExclusions.add ("id"); GSON = GsonFactory.build (fieldExclusions, null); } String estático privado getSomeJson () {String jsonStr = "[{\" id \ ": 111, \" name \ ": \" praveen \ ", \" age \ ": 16}, {\" id \ ": 222, \ "nombre \": \ "prashant \", \ "edad \": 20}] "; return jsonStr; } public static void main (String [] args) {String jsonStr = getSomeJson (); System.out.println (GSON.toJson (jsonStr)); }
Praveen Hiremath
13

Resolví este problema con anotaciones personalizadas. Esta es mi clase de anotación "SkipSerialisation":

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

}

y este es mi 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;
  }
});

Ejemplo:

public class User implements Serializable {

  public String firstName;

  public String lastName;

  @SkipSerialisation
  public String email;
}
Hibbem
fuente
55
Gson: Cómo excluir campos específicos de la serialización sin anotaciones
Ben
3
También debe agregar @Retention(RetentionPolicy.RUNTIME)a su anotación.
David Novák
9

O puede decir qué campos no se expondrán con:

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

en su clase en atributo:

private **transient** boolean nameAttribute;
lucasddaniel
fuente
17
Los campos transitorios y estáticos están excluidos por defecto; No hay necesidad de pedir excludeFieldsWithModifiers()eso.
Derek Shockey
9

Utilicé esta estrategia: excluí todos los campos que no están marcados con la anotación @SerializedName , es decir:

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);

Vuelve

{"VisibleValue": "Veré esto"}

MadMurdok
fuente
6

Otro enfoque (especialmente útil si necesita tomar una decisión para excluir un campo en tiempo de ejecución) es registrar un TypeAdapter con su instancia de gson. Ejemplo a continuación:

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

En el siguiente caso, el servidor esperaría uno de los dos valores, pero dado que ambos eran ints, entonces gson los serializaría a ambos. Mi objetivo era omitir cualquier valor que sea cero (o menos) del json que se publica en el servidor.

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;
    }
}
Damian
fuente
6

La @Transientanotación de Kotlin también hace el truco aparentemente.

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

Salida:

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

Estoy trabajando simplemente poniendo la @Exposeanotación, aquí mi versión que uso

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

En Modelclase:

@Expose
int number;

public class AdapterRestApi {

En la Adapterclase:

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);
}
devitoper
fuente
1

Tengo la versión de 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
    }
}

y cómo puede agregar esto a 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)
Michał Ziobro
fuente
0

Esto es lo que siempre uso:

El comportamiento predeterminado implementado en Gson es que los campos de objetos nulos se ignoran.

Significa que el objeto Gson no serializa campos con valores nulos a JSON. Si un campo en un objeto Java es nulo, Gson lo excluye.

Puede usar esta función para convertir algún objeto en nulo o bien configurado por usted mismo

     /**
   * 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);
  }
Julian Solarte
fuente