我正在尝试学习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这样的过滤器来匹配该字段。

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

编辑:我正在尝试查看是否有可能模仿Struts2 JSON插件的行为

使用Gson

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


编辑:
我用以下添加项重新打开了问题:

我添加了第二个相同的字段键入以进一步阐明此问题。基本上我想排除country.name但不排除countrOfBirth.name。我也不想将“国家”作为一种类型排除。
所以这些类型与我要查明和排除的对象图中的实际位置相同。

评论

从2.2版开始,我仍然无法指定要排除的字段的路径。 flexjson.sourceforge.net感觉是一个不错的选择。

看一下我对一个类似问题的回答。它基于为您的案例中的国家(地区)类型创建自定义JsonSerializer,然后为其应用ExclusionStrategy,该策略决定要序列化的字段。

stackoverflow.com/questions/3544919/…

#1 楼

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

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

private transient String name;


Gson文档中的更多详细信息

评论


它与排除注释几乎一样,因为它适用于该类的所有实例。我想要运行时动态排除。在某些情况下,我希望排除某些字段以提供较浅/受限的响应,而在另一些情况下,我希望将整个对象序列化

– Liviu T.
2011年5月7日在7:36

要注意的一件事是,瞬态影响序列化和反序列化。即使它存在于JSON中,它也会从序列化到对象时发出值。

–香港
13年4月16日在3:48

使用瞬态而不是@Expose的问题在于,您仍然必须在客户端上模拟可能包含所有可能字段的POJO。对于可能在项目之间共享的后端API,这可能是如果添加了其他字段,则会出现问题。从本质上讲,它是将这些字段列入白名单与黑名单。

– Theblang
2014年11月25日在17:08

这种方法对我不起作用,因为它不仅将字段排除在gson序列化之外,而且将该字段排除在内部应用程序序列化(使用Serializable接口)之外。

–pkk
2015年1月16日14:52

瞬态阻止字段的序列化和反序列化。这不符合我的需求。

–凤凰
15年7月20日在8:18

#2 楼

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

@Expose private Long id;


,请保留您不想序列化的任何字段。然后以这种方式创建您的Gson对象:

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


评论


是否可能有类似“ notExpose”的内容,并且仅在必须对一个字段进行序列化并且为所有字段添加注释是多余的情况下才忽略它们?

–丹尼尔·谢维列夫(Daniil Shevelev)
14年2月18日在0:19

@DaSh我最近有这种情况。编写自定义的ExclusionStrategy做到了这一点非常容易。请参阅Nishant的答案。唯一的问题是包括一堆容器类,并且用skipclass vs skipfield来摆弄(字段可以是类...)

– Keyser
2014年7月14日11:20



@DaSh我的回答确实如此。

–德里克·肖基(Derek Shockey)
2014年8月26日在3:37

多么好的解决方案。我遇到了一种情况,我希望将一个字段序列化到磁盘,但是在通过gson发送到服务器时将其忽略。很好,谢谢!

– Slynk
2015年9月16日15:15

@Danlil您应该可以使用@Expose(serialize = false,deserialize = false)

– Hrk
16年6月13日在12:28

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

    }


如果您仔细观察,它将返回trueStudent.firstNameCountry.name,这是您要排除的内容。

您需要像这样应用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}}


我假设国家对象在学生课堂中用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 }}


评论


谢谢您的回答。我想要的是创建一个ExclusionStrategy,它可以采用country.name之类的字符串,并且仅在序列化字段国家/地区时排除字段名称。它应该足够通用,适用于每个具有名为Country of Country类的country属性的类。我不想为每个课程创建一个ExclusionStrategy

– Liviu T.
2011年1月26日上午11:59

@Liviu T.我已经更新了答案。这需要通用方法。您可能会更具创造力,但我保留了它的基本内容。

– Nishant
2011年1月26日12:48

请输入更新。我正在尝试查看调用该方法时是否可能知道对象图中的位置,以便可以排除一些国家(地区)字段,但不能排除countryOfBirth(例如),因此类相同但属性不同。我已编辑问题,以阐明我要达到的目标

– Liviu T.
2011年1月26日15:08

有没有一种方法可以排除具有空值的字段?

–优素福K.
11年7月21日在11:48

该答案应标记为首选答案。与当前有更多投票的其他答案不同,此解决方案不需要您修改bean类。这是一个巨大的优势。如果其他人正在使用相同的bean类,并且您将他们希望保留的字段标记为“瞬态”怎么办?

–user64141
2015年6月17日下午0:18

#4 楼

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

注释:

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


评论


另外需要注意的是,如果要仅对序列化或反序列化使用排除策略,请使用:addSerializationExclusionStrategy或addDeserializationExclusionStrategy而不是setExclusionStrategies

– GLee
15年7月16日在1:38



完善!临时解决方案对我不起作用,因为我正在使用Realm for DB,并且我只想从Gson中排除字段,而不能从Realm中排除(临时过渡可以)

– Marcio Granzotto
16年4月15日在13:31

这应该是公认的答案。要忽略空字段,只需将f.getAnnotation(Exclude.class)!= null更改为f.getAnnotation(Exclude.class)== null

–锋利的边缘
16年8月2日在16:52

当由于其他库的需要而无法使用瞬态时,此功能非常出色。谢谢!

–马丁D
17年8月31日在8:39

对我来说也很棒,因为Android在活动之间对我的类进行了序列化,但是我只希望在使用GSON时将它们排除在外。这使我的应用程序保持相同的功能,直到它希望将它们包装起来发送给其他人为止。

–PartyTurtle
18-10-15在18:16

#5 楼

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

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

我实际上想要反函数,除非我明确使用@Expose,否则其中的所有内容都应包含在内排除它。我使用以下排除策略来实现此目的:现在,我可以轻松排除一些带有@Expose(serialize = false)@Expose(deserialize = false)批注的字段(请注意,这两个@Expose属性的默认值是true)。您当然可以使用@Expose(serialize = false, deserialize = false),但是可以通过声明字段transient来更简洁地实现(这对于这些自定义排除策略仍然有效)。

评论


为了提高效率,我可以看到使用@Expose(serialize = false,deserialize = false)而不是瞬态的情况。

–paiego
18年8月23日在18:12

@paiego可以扩展一下吗?现在,我不再使用Gson,而且我不明白为什么注释比将其标记为瞬变更有效。

–德里克·肖基(Derek Shockey)
18年8月23日在22:22

啊,我犯了一个错误,谢谢。我把挥发物误认为是瞬态的。 (例如,没有缓存,因此也没有volatile的缓存一致性问题,但是性能较低)。无论如何,您的代码效果很好!

–paiego
18/09/11在20:48



#6 楼

您可以使用gson探索json树。

尝试类似的事情:

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


还可以添加一些属性:

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


经gson 2.2.4测试。

评论


我想知道是否要摆脱在删除之前必须解析的复杂属性,这是否会对性能造成太大影响。有什么想法吗?

–本
16年11月8日在8:45

绝对不是可扩展的解决方案,想象一下如果您更改对象的结构或添加/删除内容将需要经历的所有麻烦。

–代号零
17年9月21日在18:20

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


评论


当然,可以对其进行修改以查看属性的完全限定名称,并在匹配时将其排除...

– DomenicD。
2012年4月26日在18:07

我在做下面的例子。这是行不通的。请建议私人静态决赛Gson GSON;静态{List fieldExclusions = new ArrayList (); fieldExclusions.add(“ id”); GSON = GsonFactory.build(fieldExclusions,null); }私有静态字符串getSomeJson(){字符串jsonStr =“ [{\” id \“:111,\”名称\“:\” praveen \“,\”年龄\“:16},{\” id \“: 222,\“ name \”:\“ prashant \”,\“ age \”:20}]“;返回jsonStr; } public static void main(String [] args){String jsonStr = getSomeJson(); System.out.println(GSON.toJson(jsonStr)); }

–普拉文·赫雷斯(Praveen Hiremath)
16年8月30日在6:53



#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:如何从没有注释的序列化中排除特定字段

–本
16年11月8日在8:40

您还应该将@Retention(RetentionPolicy.RUNTIME)添加到注释中。

– DavidNovák
17年5月5日在15:42

#9 楼

或者可以说Whats字段不会显示:

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


关于类的属性:

private **transient** boolean nameAttribute;


评论


默认情况下不包括瞬态和静态字段;无需为此调用excludeFieldsWithModifiers()。

–德里克·肖基(Derek Shockey)
13年7月18日在20:53

#10 楼

我使用了这种策略:
我排除了没有标有@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”:“我会看到的””}


#11 楼

Kotlin的@Transient注释显然也可以解决问题。

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


#12 楼

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

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


在以下情况下,服务器将期望两个值之一,但由于它们都是int值,因此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);
  }