想象我有一个看起来像这样的物体:
@Getter
@Setter
static class MyObject {
private String firstName;
private String lastName;
private long salary;
}
然后我有:
private static final String KEEP_ME = "$$$___KEEP_ME___$$$";
MyObject obj = new MyObject();
obj.setFirstName(KEEP_ME);
obj.setLastName(null);
obj.setSalary(1000);
我想创建一个自定义序列化器来删除任何 null 并保留等于 KEEP_ME 的字段为 null 值,我期望的结果应该如下所示:
{"firstName":null,"salary":1000}
我的序列化器看起来像:
static class NullStringSerializer implements JsonSerializer<String> {
private static final String KEEP_ME = "$$$___KEEP_ME___$$$";
@Override
public JsonElement serialize(String src, Type typeOfSrc, JsonSerializationContext context) {
if (src == null) {
return JsonNull.INSTANCE;
}
if (KEEP_ME.equals(src)) {
return JsonNull.INSTANCE;
}
return context.serialize(src, typeOfSrc);
}
}
和
Gson gsonBuilder = new GsonBuilder()
.registerTypeAdapter(String.class, new NullStringSerializer())
.create();
gsonBuilder.toJson(obj);
但这给了我:
{"salary":1000}
这种情况有什么解决办法吗?
您无法使用像
NullStringSerializer
这样的自定义值适配器来做到这一点,因为像MyObject
这样的Gson内部DTO类类型适配器的空写入策略允许所有空值或无空值。两者都通过使用 GsonBuilder#serializeNulls()
设置的单个标志进行控制。 ExclusionStrategy
接口也无济于事,因为它不知道值。这很糟糕。
我在 Spring 框架等框架中遇到了您的字符串标记方法,其中标记用于不允许包含
null
值的注释,并且实际上利用了三态方法,就像您的示例所示:
firstName
始终是 已定义,无论是否有值lastName
可能是未定义,因为它可能在MyObject
序列化有效负载中遇到或不存在这完全没问题(我看不出这个问题被否决的任何原因)。据我所知,实现三态方法的唯一两种方法是:
MyObject
(这也很糟糕)KEEP_ME
Existential.value(...)
、Existential.nullValue()
和 Existential.undefinedValue()
之类的内容,然后包装所有三态字段
Optional.of(...)
(对于值)、Optinal.empty()
(对于 null)和 Java null 来表示未定义GsonBuilder#serializeNulls()
控制,一旦调用,它就会设置 emit-nulls 策略)null
标记写入 Existential.nullValue()
或 Optional.empty()
(或 Optional.ofNullable(null)
)的 JSON 输出流。如果你同意第二种方法,可以这样实现:
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public final class ExistentialTypeAdapter
implements TypeAdapterFactory {
@Getter
private static final TypeAdapterFactory instance = new ExistentialTypeAdapter();
@Override
@Nullable
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
if ( !Optional.class.isAssignableFrom(typeToken.getRawType()) ) {
return null;
}
final ParameterizedType parameterizedType = (ParameterizedType) typeToken.getType();
final Type optionalTypeArgument = parameterizedType.getActualTypeArguments()[0];
final TypeAdapter<?> backingTypeAdapter = gson.getDelegateAdapter(this, TypeToken.get(optionalTypeArgument));
@SuppressWarnings("unchecked")
final TypeAdapter<T> adapter = (TypeAdapter<T>) new Adapter<>(backingTypeAdapter);
return adapter;
}
@AllArgsConstructor(access = AccessLevel.PRIVATE)
private static final class Adapter<T>
extends TypeAdapter<Optional<T>> {
private final TypeAdapter<T> backingTypeAdapter;
@Override
public Optional<T> read(final JsonReader in) {
throw new UnsupportedOperationException();
}
@Override
public void write(final JsonWriter out, final Optional<T> value)
throws IOException {
if ( value.isEmpty() ) {
out.jsonValue("null"); // NOTE (!) it's NOT a string, but emits the `null` token
return;
}
backingTypeAdapter.write(out, value.get());
}
}
}
public final class ExistentialTypeAdapterTest {
@Getter
@Setter
private static class MyObject {
@Nullable
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private Optional<String> firstName;
@Nullable
private String lastName;
private long salary;
}
private static final Gson gson = new GsonBuilder()
// DO NOT uncomment: .serializeNulls()
.registerTypeAdapterFactory(ExistentialTypeAdapter.getInstance())
.create();
@Test
public void test() {
final MyObject obj = new MyObject();
obj.setFirstName(Optional.empty()); // this would stand for "null" (the has-property state with explicit null value)
obj.setLastName(null); // this would stand for "undefined" (the has-no-property state in the result JSON object)
obj.setSalary(1000);
System.out.println(gson.toJson(obj));
}
}
输出:
{"firstName":null,"salary":1000}
此外,出于性能原因,我建议使用类型适配器。