Gson 仅当值等于特定模式时才序列化 null

问题描述 投票:0回答:1

想象我有一个看起来像这样的物体:

@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}

这种情况有什么解决办法吗?

java json serialization gson
1个回答
0
投票

您无法使用像

NullStringSerializer
这样的自定义值适配器来做到这一点,因为像
MyObject
这样的Gson内部DTO类类型适配器的空写入策略允许所有空值无空值。两者都通过使用
GsonBuilder#serializeNulls()
设置的单个标志进行控制。
ExclusionStrategy
接口也无济于事,因为它不知道值。这很糟糕。

我在 Spring 框架等框架中遇到了您的字符串标记方法,其中标记用于不允许包含

null
值的注释,并且实际上利用了三态方法,就像您的示例所示:

  • firstName
    始终是 已定义,无论是否有值
  • lastName
    可能是未定义,因为它可能在
    MyObject
    序列化有效负载中遇到或不存在

这完全没问题(我看不出这个问题被否决的任何原因)。据我所知,实现三态方法的唯一两种方法是:

  • 每个类编写自定义序列化器,例如
    MyObject
    (这也很糟糕)
  • 或者:
    • 删除标记字符串
      KEEP_ME
    • 引入 existential 类型(Gemini 建议的覆盖词),其中包含
      Existential.value(...)
      Existential.nullValue()
      Existential.undefinedValue()
      之类的内容,然后包装所有三态字段
      • 或使用
        Optional.of(...)
        (对于值)、
        Optinal.empty()
        (对于 null)和 Java null 来表示未定义
    • 不惜一切代价避免序列化 null 字段(由
      GsonBuilder#serializeNulls()
      控制,一旦调用,它就会设置 emit-nulls 策略)
    • raw
      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}

此外,出于性能原因,我建议使用类型适配器。

© www.soinside.com 2019 - 2024. All rights reserved.