Gson:即使具有@Expose(serialize = false)参数,也会被序列化

13
我正在为一个JSON API编写SDK,并且遇到了一个看似奇怪的问题。该API在POST数据验证方面非常严格,不允许在更新资源时出现某些参数,例如id。因此,我在我的资源类的ID字段上添加了@Expose(serialize = false)。然而,它似乎仍然序列化了这个字段,导致请求被拒绝。资源类大致如下:
public class Organisation extends BaseObject
{
    public static final Gson PRETTY_PRINT_JSON = new GsonBuilder()
            .setPrettyPrinting()
            .create();

    @Expose(serialize = false)
    @SerializedName("_id")
    private String id;

    @SerializedName("email")
    private String email;

    @SerializedName("name")
    private String name;

    @SerializedName("parent_id")
    private String parentId;

    public String toJson()
    {
        return PRETTY_PRINT_JSON.toJson(this);
    }
}

我的单元测试通过API创建了一个Organisation实例,将新创建的实例保存到测试类作为类参数,并调用一个更新方法来测试SDK的更新实现,以通过更新新资源来测试。这就是问题所在。虽然在新的Organisation上调用了toJson()方法,将其序列化为JSON以进行更新请求,但_id字段仍然存在,导致API拒绝更新。测试代码如下。请注意代码中的注释。

@Test
public void testCreateUpdateAndDeleteOrganisation() throws RequestException
{
    Organisation organisation = new Organisation();
    organisation.setParentId(this.ORGANISATION_ID);
    organisation.setName("Java Test Organisation");

    Organisation newOrganisation = this.MySDK.organisation.create(organisation);
    this.testOrganisation(newOrganisation);
    this.newOrganisation = newOrganisation;

    this.testUpdateOrganisation();
}

public void testUpdateOrganisation() throws RequestException
{
    // I tried setting ID to null, but that doesn't work either
    // even though I've set Gson to not serialise null values
    this.newOrganisation.setId(null);
    this.newOrganisation.setName(this.newName);

    // For debugging
    System.out.println(this.newOrganisation.toJson());

    Organisation updatedOrganisation = this.MySDK.organisation.update(this.newOrganisation.getId(), this.newOrganisation);

    this.testOrganisation(updatedOrganisation);
    assertEquals(newOrganisation.getName(), this.newName);

    this.testDeleteOrganisation();
}

有人能发现我做错了什么吗?我有一种感觉,这可能与实例已经具有/拥有ID的事实有关,但是如果我明确告诉它不要对其进行序列化,这就不重要了,对吧?

提前感谢您的帮助。

编辑:在 this.MySDK.organisation.update(this.newOrganisation.getId(), this.newOrganisation); 中,并没有编辑组织实例。给定的ID仅添加到SDK将要POST到的URL中 (POST /organisation/{id})


尝试将其改为“瞬态”。 - OneCricketeer
@cricket_007 我之前试过,但在反序列化时会被忽略。我使用了 @Expose 以便更好地控制它。 - Roemer
1
看过这个吗?https://futurestud.io/tutorials/gson-model-annotations-how-to-ignore-fields-with-expose - OneCricketeer
@cricket_007 是的,我知道它。你建议我从那个教程中改变什么?引用:“使用 @Expose 的替代方法是将字段声明为 transient。一个 transient 字段也不会被 (de)serialized。但是,你没有像使用 @Expose 那样完全的控制权。你不能停用一个方向,transient 总是完全关闭该属性的转换。” - Roemer
4个回答

22

感谢 @peitek 指出,除非在 GsonBuilder() 中添加 .excludeFieldsWithoutExposeAnnotation(),否则 @Expose 将被忽略。然而,我选择不走这条路,因为这将要求我在我的模型类的每个参数中添加 @Expose,仅仅为了忽略一个字段的序列化。相反,我编写了一个 ExclusionStrategy,该策略检查参数上是否存在自定义的 SkipSerialisation 注解。我实现了以下内容:

具有上述策略的完整的 GsonBuilder

public static final Gson PRETTY_PRINT_JSON = new GsonBuilder()
        .addSerializationExclusionStrategy(new ExclusionStrategy()
        {
            @Override
            public boolean shouldSkipField(FieldAttributes f)
            {
                return f.getAnnotation(SkipSerialisation.class) != null;
            }

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

并且有注释:

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

现在我只需要做

@SkipSerialisation
@SerializedName("_id")
private String id;

而且它有效!


f.getAnnotation(SkipSerialisation.class) != null 可以替换为 f.isAnnotationPresent(SkipSerialisation.class) - Lyubomyr Shaydariv
@LyubomyrShaydariv 更好的是!我会使用那个。 - Roemer
1
@LyubomyrShaydariv 这是哪个版本的Gson?我没有这个方法。 - Roemer
对不起对不起对不起!我混淆了 https://docs.oracle.com/javase/7/docs/api/java/lang/reflect/AnnotatedElement.html#isAnnotationPresent(java.lang.Class)。真是丢人! - Lyubomyr Shaydariv
@LyubomyrShaydariv 啊!没问题。除此之外,你是Java的天才。 - Roemer

8
如您在评论中提到的那样,@Expose 应该比 transient 更好。需要注意的是,默认的 Gson 实例不会处理 @Expose 注解!无论您设置了什么选项,它都会忽略它。
如果您想要激活 @Expose 选项,您需要自定义 Gson。基于您上面的代码,将其更改为:
public static final Gson PRETTY_PRINT_JSON = new GsonBuilder()
        .setPrettyPrinting()
        .excludeFieldsWithoutExposeAnnotation();
        .create();

您的@Expose(serialize = false)应该是活动的,并在序列化过程中被排除。

2
你好,感谢你的回答。我原以为 excludeFieldsWithoutExposeAnnotation() 会按照其名字所示的那样工作,但如果我想要做一些特定的事情,Expose 仍然可以发挥作用。这是否意味着我必须在所有模型的每个字段中都添加 Expose? - Roemer
是的,同意。那个函数名不太理想。而且,是的,你需要在相关模型的每个字段上至少添加一个@Expose() - peitek
谢谢。查看文档后,它清楚地说明:如果我们使用 new Gson() 创建了 Gson 对象,并执行 toJson() 和 fromJson() 方法,则 @Expose 不会对序列化和反序列化产生任何影响。(...)https://howtodoinjava.com/gson/gson-exclude-or-ignore-fields/ - O-9

4

我想在这里补充一下,您可以按照以下方式使用Expose:

builder.addSerializationExclusionStrategy(new ExclusionStrategy() {
        @Override
        public boolean shouldSkipField(FieldAttributes f) {
            Expose annotation = f.getAnnotation(Expose.class);
            if(annotation != null)
                return !annotation.serialize();
            else
                return false;
        }

        @Override
        public boolean shouldSkipClass(Class<?> clazz) {
            Expose annotation = clazz.getAnnotation(Expose.class);
            if(annotation != null)
                return !annotation.serialize();
            else
                return false;
        }
    });
    builder.addDeserializationExclusionStrategy(new ExclusionStrategy() {
        @Override
        public boolean shouldSkipField(FieldAttributes f) {
            Expose annotation = f.getAnnotation(Expose.class);
            if(annotation != null)
                return !annotation.deserialize();
            else
                return false;
        }

        @Override
        public boolean shouldSkipClass(Class<?> clazz) {
            Expose annotation = clazz.getAnnotation(Expose.class);
            if(annotation != null)
                return !annotation.deserialize();
            else
                return false;
        }
    });

-1
    GsonBuilder()
        /**   WARN : the function result is :
         *         just Serialization the field and class which with @Expose
         * */
        /*.excludeFieldsWithoutExposeAnnotation()*/



          /** addXxxxxxxxxExclusionStrategy Function is the best selection
                 define yourself exclusion rule
          */
        .addSerializationExclusionStrategy(
            object : ExclusionStrategy {
                override fun shouldSkipField(fieldAttr: FieldAttributes?): Boolean {
                    return fieldAttr?.getAnnotation(Expose::class.java)?.serialize == false
                }

                override fun shouldSkipClass(clazz: Class<*>?): Boolean {
                    return clazz?.getAnnotation(Expose::class.java)?.serialize == false
                }
            })
        .addDeserializationExclusionStrategy(object : ExclusionStrategy {

            override fun shouldSkipField(fieldAttr: FieldAttributes?): Boolean {
                return fieldAttr?.getAnnotation(Expose::class.java)?.deserialize == false
            }

            override fun shouldSkipClass(clazz: Class<*>?): Boolean {
                return clazz?.getAnnotation(Expose::class.java)?.deserialize == false
            }
        })
        .create()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接