Gson序列化:如果为空,则排除一些字段,但不是全部

ql3eal8s  于 2022-11-06  发布在  其他
关注(0)|答案(1)|浏览(255)

给定POJO:

class Person {
    @Expose
    String name;
    @Expose
    String phone;
    @Expose
    String fax;
}

我希望“phone”在任何时候都是序列化的,而“fax”只有在它不为空时才是序列化的。因此,假设一个没有电话或传真的人“John”:
目前:

{ "name": "John", "phone": null, "fax": null }

我需要:

{ "name": "John", "phone": null }

有没有类似这样的东西:

@Expose (serialize_if_null = false)

transient不起作用,因为如果它有值,我仍然希望它被序列化。
使用ExclusionStrategy,我可以定义字段,但似乎找不到获取值的方法。
谢谢

41zrol4v

41zrol4v1#

使用ExclusionStrategy,我可以定义字段,但似乎找不到获取值的方法。
是的,它没有提供确定当前字段值的方法,这是因为Gson ReflectiveTypeAdapterFactory的内部工作方式(BoundField.serializedfinal,并且只解析一次):
第一个
这种行为不能改变,但是我相信,为了避免混淆概念和使应用程序组件松散耦合(将来从Gson迁移时,只需要修改相应的DTO类),将应用程序对象和它们的序列化表示(请参见Data Transfer Object模式)分离开来是一个很好的设计选择。
如果您不介意将DTO引入到应用程序中,则可以为这两种情况创建单独得DTO类:根据fax字段值保留phone并丢弃fax
第一个
在本例中,序列化是直接的:

final Gson gson = new GsonBuilder()
        .serializeNulls()
        .create();
final Person person = new Person();
person.name = "John";
final PersonDto personDto = person.fax == null
        ? new PersonDto(person)
        : new PersonDtoWithFax(person);
System.out.println(gson.toJson(personDto));

如果您不想引入分离的DTO概念本身,那么您可能希望实现一个自定义序列化程序,该程序在实现上稍微复杂一些,并且由于属性名称硬编码而可能在某种程度上容易出错(当然,您可以进行良好的测试,或者从java.lang.reflect.Field示例中提取名称)。

final class SpecialJsonSerializer<T>
        implements JsonSerializer<T> {

    private final Gson gson; // Unfortunately, Gson does not provide much from JsonSerialiationContext, so we have to get it ourselves
    private final Iterable<String> excludeIfNull;

    private SpecialJsonSerializer(final Gson gson, final Iterable<String> excludeIfNull) {
        this.gson = gson;
        this.excludeIfNull = excludeIfNull;
    }

    static <T> JsonSerializer<T> getSpecialJsonSerializer(final Gson gson, final Iterable<String> excludeIfNull) {
        return new SpecialJsonSerializer<>(gson, excludeIfNull);
    }

    @Override
    public JsonElement serialize(final T object, final Type type, final JsonSerializationContext context) {
        // context.serialize(person, type) cannot work due to infinite recursive serialization
        // therefore the backing Gson instance is used
        final JsonObject jsonObject = gson.toJsonTree(object, type).getAsJsonObject();
        for ( final String propertyName : excludeIfNull ) {
            final JsonElement property = jsonObject.get(propertyName);
            if ( property != null && property.isJsonNull() ) {
                jsonObject.remove(propertyName);
            }
        }
        return jsonObject;
    }

}

我不是很确定,但我认为,从内存消耗的Angular 来看,为序列化目的创建JSON树比使用DTO可能稍微昂贵一些(至少因为JsonElement结构更复杂)。

// Both Gson instances must have serializeNulls()
final Gson gson = new GsonBuilder()
        .serializeNulls()
        .create();
final Gson gsonWrapper = new GsonBuilder()
        .serializeNulls()
        .registerTypeAdapter(Person.class, getSpecialJsonSerializer(gson, singletonList("fax")))
        .create();
final Person person = new Person();
person.name = "John";
System.out.println(gsonWrapper.toJson(person));

两种解决方案都输出:
{“姓名”:“约翰”,“电话”:空}

相关问题