首页 文章

Gson:如何在没有注释的情况下从序列化中排除特定字段

提问于
浏览
373

我正在努力学习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并为字段名称添加ExclusionStrategy,如 firstNamecountry ,但我似乎无法排除某些字段的属性,如 country.name .

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

对于这个问题的解决方案,我将不胜感激 .

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

谢谢

Edit :我'm trying to see if it'可以模仿Struts2 JSON plugin的行为

使用Gson

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

Edit: 我通过以下补充重新打开了这个问题:

我添加了第二个相同类型的字段,以进一步澄清这个问题 . 基本上我想排除 country.name 而不是 countrOfBirth.name . 我也不知道对象图中我想要查明和排除的实际位置 .

13 回答

  • 0

    你可以用gson探索json树 .

    尝试这样的事情:

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

    您还可以添加一些属性:

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

    用gson 2.2.4测试 .

  • 13

    或者可以说什么字段不会曝光:

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

    你的 class 属性:

    private **transient** boolean nameAttribute;
    
  • 571

    我遇到了这个问题,其中我只想从序列化中排除少量字段,因此我开发了一个相当简单的解决方案,它使用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 来更简洁地完成(这仍然适用于这些自定义排除策略) .

  • 6

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

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

    在阅读了我发现的所有可用答案之后,在我的情况下,最灵活的是使用自定义 @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();
    
  • 15

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

    @Expose private Long id;
    

    省去您不想序列化的任何字段 . 然后以这种方式创建您的Gson对象:

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

    我正在使用 @Expose 注释,这是我使用的版本

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

    Model class :

    @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);
    }
    
  • 213

    我使用了这个策略:我排除了 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”:“我会看到这个”}

  • 0

    我有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)
    
  • 16

    所以,你想 exclude 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 . ”,“姓名缩写”:“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 }}
    

    EDIT: 根据要求添加了更多信息 .

    ExclusionStrategy 会做的,但你需要通过"Fully Qualified Field Name" . 见下文:

    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 }}
    
  • 73

    您通常不需要序列化的任何字段都应该使用“transient”修饰符,这也适用于json序列化程序(至少它适用于我使用过的一些,包括gson) .

    如果您不希望名称显示在序列化的json中,请为其提供一个临时关键字,例如:

    private transient String name;
    

    More details in the Gson documentation

  • 183

    我用自定义注释解决了这个问题 . 这是我的“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;
    }
    
  • 9

    另一种方法(如果您需要决定在运行时排除字段,尤其有用)是使用您的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;
        }
    }
    

相关问题