【问题标题】:Gson: How to exclude specific fields from Serialization without annotationsGson:如何在没有注释的情况下从序列化中排除特定字段
【发布时间】:2011-06-15 17:32:12
【问题描述】:

我正在努力学习 Gson,但我正在努力解决字段排除问题。这是我的课

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

我可以使用 GsonBuilder 并为 firstNamecountry 之类的字段名称添加 ExclusionStrategy,但我似乎无法排除某些字段的属性,例如 country.name

使用方法public boolean shouldSkipField(FieldAttributes fa),FieldAttributes 不包含足够的信息来匹配字段与过滤器,如country.name

P.S:我想避免注释,因为我想对此进行改进并使用 RegEx 过滤字段。

编辑:我正在尝试看看是否可以模仿Struts2 JSON plugin的行为

使用 Gson

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

编辑: 我重新打开了这个问题,并添加了以下内容:

我添加了第二个具有相同类型的字段以进一步澄清这个问题。基本上我想排除country.name,但不排除countrOfBirth.name。我也不想将 Country 排除为一种类型。 所以类型是相同的,它是我想要精确定位和排除的对象图中的实际位置。

【问题讨论】:

  • 从 2.2 版开始,我仍然无法指定要排除的字段路径。 flexjson.sourceforge.net 感觉是个不错的选择。
  • my answer 上查看一个非常相似的问题。它基于为某些类型创建自定义JsonSerializer - 在您的情况下为Country - 然后应用ExclusionStrategy 来决定要序列化的字段。

标签: java json serialization gson


【解决方案1】:

一般你不想序列化的任何字段都应该使用“transient”修饰符,这也适用于 json 序列化器(至少它适用于我使用过的一些,包括 gson)。

如果您不希望名称出现在序列化的 json 中,请给它一个临时关键字,例如:

private transient String name;

More details in the Gson documentation

【讨论】:

  • 它与排除注释几乎相同,因为它适用于该类的所有实例。我想要运行时动态排除。在某些情况下,我希望排除某些字段以提供更轻松/受限制的响应,而在其他情况下,我希望将完整的对象序列化
  • 需要注意的一点是瞬态会影响序列化和反序列化。它还将从被序列化到对象中发出值,即使它存在于 JSON 中。
  • 使用transient 而不是@Expose 的问题是您仍然必须在客户端上模拟一个POJO,其中包含所有可能进入的字段。在后端的情况下可能在项目之间共享的 API,如果添加了其他字段,这可能会出现问题。本质上,它是字段的白名单与黑名单。
  • 这种方法对我不起作用,因为它不仅从 gson 序列化中排除了该字段,而且从内部应用序列化中排除了该字段(使用 Serializable 接口)。
  • transient 防止字段的序列化和反序列化。这不符合我的需要。
【解决方案2】:

Nishant 提供了一个很好的解决方案,但还有一种更简单的方法。只需使用@Expose 注解标记所需的字段,例如:

@Expose private Long id;

省略您不想序列化的任何字段。然后用这种方式创建你的 Gson 对象:

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

【讨论】:

  • 是否可以有类似“notExpose”的内容,并且只在必须序列化除一个字段之外的所有字段并且为所有字段添加注释是多余的情况下忽略那些?
  • @DaSh 我最近遇到过这样的情况。编写一个自定义的 ExclusionStrategy 非常容易做到这一点。请参阅 Nishant 的回答。唯一的问题是包含一堆容器类并摆弄skipclass与skipfield(字段可以是类......)
  • @DaSh My answer 正是这样做的。
  • 多么棒的解决方案。我遇到了一个场景,我希望将一个字段序列化到磁盘,但在通过 gson 将其发送到服务器时被忽略。完美,谢谢!
  • @Danlil 你应该可以使用@Expose(serialize = false, deserialize = false)
【解决方案3】:

所以,您想排除 firstNamecountry.name。这是您的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"));
        }

    }

如果您仔细观察,它会为 Student.firstNameCountry.name 返回 true,这是您要排除的内容。

你需要像这样申请ExclusionStrategy

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

这会返回:

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

我假设 country 对象在学生类中使用 id = 91L 初始化。


你可能会喜欢。例如,您不想序列化名称中包含“name”字符串的任何字段。这样做:

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

这将返回:

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

编辑:根据要求添加了更多信息。

这个ExclusionStrategy 会做这件事,但你需要传递“完全合格的字段名称”。见下文:

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

    }

下面是我们如何通用地使用它。

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

返回:

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

【讨论】:

  • 感谢您的回答。我想要的是创建一个排除策略,它可以采用像country.name 这样的字符串,并且在序列化字段country 时仅排除字段name。它应该足够通用,以适用于每个具有 Country 类的名为 country 的属性的类。我不想为每个班级都创建一个 ExclusionStrategy
  • @Liviu T. 我已经更新了答案。这需要通用方法。你可能会更有创意,但我保持了它的元素。
  • Ty 更新。我正在尝试查看是否有可能知道我在对象图中的位置,当它调用的方法时,我可以排除国家的某些字段,但不能排除 countryOfBirth(例如)相同的类但不同的属性。我已经编辑了我的问题以澄清我想要实现的目标
  • 有没有办法排除具有空值的字段?
  • 此答案应标记为首选答案。与当前拥有更多投票的其他答案不同,此解决方案不需要您修改 bean 类。这是一个巨大的优势。如果其他人正在使用同一个 bean 类,而您将他们想要持久化的字段标记为“瞬态”怎么办?
【解决方案4】:

在阅读了所有可用的答案后,我发现,就我而言,最灵活的是使用自定义 @Exclude 注释。因此,我为此实施了简单的策略(我不想使用@Expose 标记所有字段,也不想使用与应用程序中Serializable 序列化冲突的transient):

注释:

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

策略:

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

用法:

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

【讨论】:

  • 补充说明,如果您想将排除策略仅用于序列化或仅反序列化,请使用:addSerializationExclusionStrategyaddDeserializationExclusionStrategy 而不是 setExclusionStrategies
  • 完美!瞬态解决方案对我不起作用,因为我将 Realm 用于 DB,并且我只想从 Gson 中排除一个字段,而不是 Realm(至于瞬态)
  • 这应该是公认的答案。要忽略空字段,只需将 f.getAnnotation(Exclude.class) != null 更改为 f.getAnnotation(Exclude.class) == null
  • 因为其他库的需要而无法使用transient 时非常好。谢谢!
  • 对我来说也很棒,因为 Android 会在活动之间序列化我的类,但我只希望在使用 GSON 时将它们排除在外。这让我的应用程序继续以相同的方式运行,直到它想要将它们包装起来发送给其他人。
【解决方案5】:

我遇到了这个问题,我有少量字段只想从序列化中排除,因此我开发了一个相当简单的解决方案,它使用 Gson 的 @Expose 注释和自定义排除策略。

使用@Expose 的唯一内置方法是设置GsonBuilder.excludeFieldsWithoutExposeAnnotation(),但顾名思义,没有显式@Expose 的字段将被忽略。由于我只有几个要排除的字段,我发现将注释添加到每个字段的前景非常麻烦。

我实际上想要反过来,除非我明确使用 @Expose 排除它,否则它会包含所有内容。我使用以下排除策略来完成此操作:

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

现在我可以轻松地排除带有@Expose(serialize = false)@Expose(deserialize = false) 注释的一些字段(请注意,@Expose 属性的默认值都是true)。您当然可以使用@Expose(serialize = false, deserialize = false),但更简洁地通过声明字段transient 来实现(这些自定义排除策略仍然有效)。

【讨论】:

  • 为了提高效率,我可以看到一个使用@Expose(serialize = false, deserialize = false) 而不是瞬态的案例。
  • @paiego 你能扩展一下吗?我现在已经使用 Gson 多年了,我不明白为什么注释比将其标记为瞬态更有效。
  • 啊,我犯了一个错误,谢谢你发现这个。我误认为 volatile 是瞬态的。 (例如,没有缓存,因此 volatile 没有缓存一致性问题,但性能较差)无论如何,您的代码运行良好!
【解决方案6】:

您可以使用 gson 探索 json 树。

试试这样的:

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

你也可以添加一些属性:

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

使用 gson 2.2.4 测试。

【讨论】:

  • 我想知道如果您想摆脱在删除之前必须解析的复杂属性,这是否会对性能造成太大影响。想法?
  • 绝对不是一个可扩展的解决方案,想象一下,如果您更改对象的结构或添加/删除内容,您将需要经历的所有头痛。
【解决方案7】:

我想出了一个类工厂来支持这个功能。传入要排除的字段或类的任意组合。

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

    }
}

要使用,请创建两个列表(每个列表都是可选的),然后创建您的 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);
}

【讨论】:

  • 当然,这可以修改为查看属性的完全限定名,并在匹配时排除...
  • 我在做下面的例子。这是行不通的。请建议私有静态最终 Gson GSON;静态 { List fieldExclusions = new ArrayList(); fieldExclusions.add("id"); GSON = GsonFactory.build(fieldExclusions, null); } private static String getSomeJson() { String jsonStr = "[{\"id\":111,\"name\":\"praveen\",\"age\":16},{\"id\": 222,\"姓名\":\"prashant\",\"年龄\":20}]";返回 jsonStr; } public static void main(String[] args) { String jsonStr = getSomeJson(); System.out.println(GSON.toJson(jsonStr)); }
【解决方案8】:

我用自定义注释解决了这个问题。 这是我的“SkipSerialisation”注解类:

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

}

这是我的 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;
  }
});

例子:

public class User implements Serializable {

  public String firstName;

  public String lastName;

  @SkipSerialisation
  public String email;
}

【讨论】:

  • Gson:如何从序列化中排除特定字段没有注释
  • 您还应该在注释中添加@Retention(RetentionPolicy.RUNTIME)
【解决方案9】:

Kotlin 的 @Transientannotation 显然也可以解决问题。

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

输出:

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

【讨论】:

    【解决方案10】:

    或者可以说什么字段不会暴露:

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

    关于你的属性类:

    private **transient** boolean nameAttribute;
    

    【讨论】:

    • 默认排除瞬态和静态字段;无需为此致电excludeFieldsWithModifiers()
    【解决方案11】:

    我使用了这个策略: 我排除了 not 标记有 @SerializedName 注释的每个字段,即:

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

    返回

    {"VisibleValue":"我会看到这个"}

    【讨论】:

      【解决方案12】:

      另一种方法(如果您需要在运行时做出排除字段的决定特别有用)是在您的 gson 实例中注册一个 TypeAdapter。下面的例子:

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

      在下面的情况下,服务器会期望两个值之一,但由于它们都是整数,因此 gson 会将它们都序列化。我的目标是从发布到服务器的 json 中省略任何为零(或更小)的值。

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

      【讨论】:

        【解决方案13】:

        我只是将@Expose 注释放在这里,这是我使用的版本

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

        Model 类中:

        @Expose
        int number;
        
        public class AdapterRestApi {
        

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

        【讨论】:

          【解决方案14】:

          我有 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
              }
          }
          

          以及如何将其添加到 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)
          

          【讨论】:

            【解决方案15】:

            这是我经常使用的:

            在 Gson 中实现的默认行为是忽略空对象字段。

            意味着 Gson 对象不会将具有空值的字段序列化为 JSON。如果 Java 对象中的某个字段为 null,Gson 会将其排除在外。

            您可以使用此功能将某些对象转换为null或您自己设置好

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

            【讨论】:

              【解决方案16】:

              Android Kotlin

              有了这个,Json 工作变得如此简单。

              请关注此视频:JsonToKotlin - YouTube

              文档:JsonToKotlin - GitHub

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2018-11-03
                • 2011-10-14
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2020-07-19
                • 1970-01-01
                相关资源
                最近更新 更多