Gson序列化:如果某些字段为null,但不全部排除某些字段

sik*_*art 5 java gson

给定一个POJO:

class Person {
    @Expose
    String name;
    @Expose
    String phone;
    @Expose
    String fax;
}
Run Code Online (Sandbox Code Playgroud)

我希望“电话”始终都进行序列化,但是仅当“传真”不为null时才进行序列化。因此,假设没有电话或传真的“约翰”人:

当前:

{ "name": "John", "phone": null, "fax": null }
Run Code Online (Sandbox Code Playgroud)

我需要的:

{ "name": "John", "phone": null }
Run Code Online (Sandbox Code Playgroud)

是否有类似的东西:

@Expose (serialize_if_null = false)
Run Code Online (Sandbox Code Playgroud)

瞬态不起作用,因为如果它有值,我仍然希望将其序列化。

使用ExclusionStrategy,我可以定义字段,但是似乎找不到找到值的方法。

谢谢

Lyu*_*riv 6

使用 ExclusionStrategy,我可以定义该字段,但我似乎无法找到获取该值的方法。

是的,它不提供确定当前字段值的方法。这是因为 GsonReflectiveTypeAdapterFactory内部的工作方式(BoundField.serialized仅final解决一次):

@Override public boolean writeField(Object value) throws IOException, IllegalAccessException {
  if (!serialized) return false;
  Object fieldValue = field.get(value);
  return fieldValue != value; // avoid recursion for example for Throwable.cause
}
Run Code Online (Sandbox Code Playgroud)
for (BoundField boundField : boundFields.values()) {
  if (boundField.writeField(value)) {
    out.name(boundField.name);
    boundField.write(out, value);
  }
}
Run Code Online (Sandbox Code Playgroud)

这种行为无法更改,但我相信隔离应用程序对象及其序列化表示(请参阅数据传输对象模式)是一个很好的设计选择,以免混合概念并使应用程序组件松散耦合(有一天从 Gson 迁移只需要仅针对相应的 DTO 类进行修改)。

如果您愿意将 DTO 引入到您的应用程序中,那么您可以为这两种情况创建单独的 DTO 类:根据字段值保留phone和丢弃。faxfax

class PersonDto {
    @Expose String name;
    @Expose String phone;
    PersonDto(final Person person) {
        name = person.name;
        phone = person.phone;
    }
}
Run Code Online (Sandbox Code Playgroud)
class PersonDtoWithFax extends PersonDto {
    @Expose String fax;
    PersonDtoWithFax(final Person person) {
        super(person);
        fax = person.fax;
    }
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,序列化是直接的:

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));
Run Code Online (Sandbox Code Playgroud)

如果您不想引入隔离的 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;
    }

}
Run Code Online (Sandbox Code Playgroud)

我不太确定,但我认为从内存消耗的角度来看,为序列化目的创建 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));
Run Code Online (Sandbox Code Playgroud)

两种解决方案输出:

{“姓名”:“约翰”,“电话”:空}