Gson如何为特定的类或字段序列化null值

42

我希望能够序列化某个字段或类的空值。

在 GSON 中,选项 serializeNulls() 适用于整个 JSON。

例如:

class MainClass {
    public String id;
    public String name;
    public Test test;
}

class Test {
    public String name;
    public String value;    
} 

MainClass mainClass = new MainClass();
mainClass.id = "101"
// mainClass has no name.
Test test = new Test();
test.name = "testName";
test.value = null;
mainClass.test = test;    

使用GSON创建JSON:

GsonBuilder builder = new GsonBuilder().serializeNulls();
Gson gson = builder.create();
System.out.println(gson.toJson(mainClass));

当前输出:

{
    "id": "101",
    "name": null,
    "test": {
        "name": "testName",
        "value": null
    }
}

期望输出:

{
    "id": "101",
    "test": {
        "name": "testName",
        "value": null
    }
}

如何实现期望的输出?

首选解决方案应具有以下特性:

  • 默认情况下序列化null值,
  • 对于带有特定注释的字段序列化null值。

@DatoMumladze 我更新了我的问题。 - Martin
我在Gson中找不到这个功能。这里有一些有趣的链接。或者你可以使用Jackson将对象序列化为JSON,并使用此注释@JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)来排除特定字段的空值。 - Davit Mumladze
6个回答

20

我有一个类似于Aleksey的解决方案,但可以应用于任何类中的一个或多个字段(Kotlin示例):

创建一个新的注释,用于表示应将其序列化为null的字段:

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FIELD)
annotation class SerializeNull

创建一个TypeAdapterFactory,它检查一个类是否有用这个注解标注的字段,并在写入对象时从JsonTree中删除那些为null且没有用该注解标注的字段。
class SerializableAsNullConverter : TypeAdapterFactory {

    override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
        fun Field.serializedName() = declaredAnnotations
            .filterIsInstance<SerializedName>()
            .firstOrNull()?.value ?: name
        val declaredFields = type.rawType.declaredFields
        val nullableFieldNames = declaredFields
            .filter { it.declaredAnnotations.filterIsInstance<SerializeNull>().isNotEmpty() }
            .map { it.serializedName() }
        val nonNullableFields = declaredFields.map { it.serializedName() } - nullableFieldNames

        return if (nullableFieldNames.isEmpty()) {
            null
        } else object : TypeAdapter<T>() {
            private val delegateAdapter = gson.getDelegateAdapter(this@SerializableAsNullConverter, type)
            private val elementAdapter = gson.getAdapter(JsonElement::class.java)

            override fun write(writer: JsonWriter, value: T?) {
                val jsonObject = delegateAdapter.toJsonTree(value).asJsonObject
                nonNullableFields
                    .filter { jsonObject.get(it) is JsonNull }
                    .forEach { jsonObject.remove(it) }
                val originalSerializeNulls = writer.serializeNulls
                writer.serializeNulls = true
                elementAdapter.write(writer, jsonObject)
                writer.serializeNulls = originalSerializeNulls
            }

            override fun read(reader: JsonReader): T {
                return delegateAdapter.read(reader)
            }
        }
    }
}

使用以下代码将适配器注册到你的Gson实例中:

val builder = GsonBuilder().registerTypeAdapterFactory(SerializableAsNullConverter())

同时,在要置为空的字段上进行注释:

class MyClass(val id: String?, @SerializeNull val name: String?)

序列化结果:

val myClass = MyClass(null, null)
val gson = builder.create()
val json = gson.toJson(myClass)

JSON:

{
    "name": null
}

2
谢谢!我在一个Java项目中使用Retrofit重新实现了它,这是我发现的最好的可定制的GSON可空类型解决方案。请注意,在Java中,您需要从declaredField.getAnnotation(SerializedName.class).value()中提取序列化字段名称,以便更复杂的变量名称能够正常工作。 - Arvoreniad
很有趣的@Arvoreniad,我已经使用Kotlin实现测试了这个案例,不需要额外的逻辑来保留序列化名称。委托适配器应该确保这一点。 - Joris
我在Android上使用Kotlin,注意到了和@Arvoreniad一样的问题。带有@SerializedName注释的字段,如果为null,在生成的JSON中将被序列化为null。 - Eric
1
@Arvoreniad,你能发一下你的Java解决方案吗? - Zookey
@Zookey,我刚刚发布了解决方案作为额外的答案。 - Arvoreniad
这忽略了嵌套的JSON。你有什么想法为什么会这样? - Raghunandan

6

我有一个接口,用于检查对象何时应该序列化为null:

public interface JsonNullable {
  boolean isJsonNull();
}

对应的TypeAdapter(仅支持写入)

public class JsonNullableAdapter extends TypeAdapter<JsonNullable> {

  final TypeAdapter<JsonElement> elementAdapter = new Gson().getAdapter(JsonElement.class);
  final TypeAdapter<Object> objectAdapter = new Gson().getAdapter(Object.class);

  @Override
  public void write(JsonWriter out, JsonNullable value) throws IOException {
    if (value == null || value.isJsonNull()) {
      //if the writer was not allowed to write null values
      //do it only for this field
      if (!out.getSerializeNulls()) {
        out.setSerializeNulls(true);
        out.nullValue();
        out.setSerializeNulls(false);
      } else {
        out.nullValue();
      }
    } else {
      JsonElement tree = objectAdapter.toJsonTree(value);
      elementAdapter.write(out, tree);
    }
  }

  @Override
  public JsonNullable read(JsonReader in) throws IOException {
    return null;
  }
}

使用方法如下:

public class Foo implements JsonNullable {
  @Override
  public boolean isJsonNull() {
    // You decide
  }
}

在这个类中,Foo 值应该被序列化为 null。请注意,foo 的值本身必须不为 null,否则自定义适配器注释将被忽略。
public class Bar {
  @JsonAdapter(JsonNullableAdapter.class)
  public Foo foo = new Foo();
}

这个解决方案的性能非常差,因为它创建了一个JsonElement树作为中间数据结构,而不是直接写入JsonWriter。 - Miro Spönemann
如果我记得正确的话,objectAdapter.toJsonTree 用于将对象序列化为普通的 JSON。 - Oleksii Masnyi
toJsonTree() 用于创建 JsonElements 的树形结构。直接写入 JsonWriter 而不创建中间数据结构会更快。实现此功能的通用 Gson 类是 ReflectiveTypeAdapterFactory。 - Miro Spönemann
你可以尝试编写一个TypeAdapterFactory,并使用gson.getDelegateAdapter(...)获取正确的类型适配器;有关该方法的更多信息,请参阅其JavaDoc。 - Miro Spönemann

5

对于那些寻找@Joris优秀答案的Java版本的人,下面的代码应该可以胜任。它基本上只是Kotlin的翻译,并对获取属性的序列化名称进行了小改进,以确保在序列化名称与属性名称不同时始终起作用(请参见原始答案中的注释)。

这是实现:

public class NullableAdapterFactory implements TypeAdapterFactory {
    @Override
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
        Field[] declaredFields = type.getRawType().getDeclaredFields();
        List<String> nullableFieldNames = new ArrayList<>();
        List<String> nonNullableFieldNames = new ArrayList<>();

        for (Field declaredField : declaredFields) {
            if (declaredField.isAnnotationPresent(JsonNullable.class)) {
                if (declaredField.getAnnotation(SerializedName.class) != null) {
                    nullableFieldNames.add(declaredField.getAnnotation(SerializedName.class).value());
                } else {
                    nullableFieldNames.add(declaredField.getName());
                }
            } else {
                if (declaredField.getAnnotation(SerializedName.class) != null) {
                    nonNullableFieldNames.add(declaredField.getAnnotation(SerializedName.class).value());
                } else {
                    nonNullableFieldNames.add(declaredField.getName());
                }
            }
        }

        if (nullableFieldNames.size() == 0) {
            return null;
        }

        TypeAdapter<T> delegateAdapter = gson.getDelegateAdapter(NullableAdapterFactory.this, type);
        TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);

        return new TypeAdapter<T>() {
            @Override
            public void write(JsonWriter out, T value) throws IOException {
                JsonObject jsonObject = delegateAdapter.toJsonTree(value).getAsJsonObject();
                for (String name: nonNullableFieldNames) {
                    if (jsonObject.has(name) && jsonObject.get(name) instanceof JsonNull) {
                        jsonObject.remove(name);
                    }
                }
                
                boolean originalSerializeNulls = out.getSerializeNulls();
                out.setSerializeNulls(true);
                elementAdapter.write(out, jsonObject);
                out.setSerializeNulls(originalSerializeNulls);
            }

            @Override
            public T read(JsonReader in) throws IOException {
                return delegateAdapter.read(in);
            }

        };
    }
}

这是用于标记目标属性的@JsonNullable注解:

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

我将其实现为对象类上的@JsonAdapter(NullableAdapterFactory.class)注释,而不是将其注册为GsonBuilder实例上的TypeAdapterFactory,因此我的对象类看起来有些像这样:

@JsonAdapter(NullableAdapterFactory.class)
public class Person {
  public String firstName;
  public String lastName;
  
  @JsonNullable
  public String someNullableInfo;
}


然而,如果更喜欢另一种方法,这段代码也应该同样有效。

0

补充@Arvoreniad的答案

两个补充是在将输出设置为true后,重置JsonWriter中的null序列化状态,并使用Gson的字段命名策略获取字段名称。

public class SerializeNullTypeAdapterFactory implements TypeAdapterFactory {
    /**
     * {@inheritDoc}
     */
    @Override
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
        Field[] declaredFields = type.getRawType().getDeclaredFields();
        List<String> nullableFields = new ArrayList<>();
        List<String> nonNullableFields = new ArrayList<>();
        FieldNamingStrategy fieldNamingStrategy = gson.fieldNamingStrategy();

        for (Field declaredField : declaredFields) {
            // The Gson FieldNamingStrategy will handle the @SerializedName annotation + casing conversions
            final String fieldName = fieldNamingStrategy.translateName(declaredField);

            if (declaredField.isAnnotationPresent(JsonNullable.class)) {
                nullableFields.add(fieldName);
            } else {
                nonNullableFields.add(fieldName);
            }
        }

        if (nullableFields.isEmpty()) {
            return null;
        }

        TypeAdapter<T> delegateAdapter = gson.getDelegateAdapter(this, type);
        TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);

        return new TypeAdapter<T>() {
            @Override
            public void write(JsonWriter out, T value) throws IOException {
                JsonObject jsonObject = delegateAdapter.toJsonTree(value).getAsJsonObject();

                nonNullableFields.forEach((var name) -> {
                    if (jsonObject.has(name) && (jsonObject.get(name) instanceof JsonNull)) {
                        jsonObject.remove(name);
                    }
                });

                boolean serializeNulls = out.getSerializeNulls();
                out.setSerializeNulls(true);

                elementAdapter.write(out, jsonObject);

                // Reset default (in case JsonWriter is reused)
                out.setSerializeNulls(serializeNulls);
            }

            @Override
            public T read(JsonReader in) throws IOException {
                return delegateAdapter.read(in);
            }
        };
    }
}

0

我从这里的各种答案中汲取了一些想法。

这个实现:

  • 让你在运行时选择 JSON 是
    • null
      • JsonNullable.isJsonNull() == true时发生
    • 非 null
      • JsonNullable.isJsonNull() == false时发生
    • 从 JSON 中省略(对于 HTTP PATCH 请求很有用)
      • Parent 中包含 JsonNullable 的字段为 null 时发生
  • 不需要注释
  • 通过使用 TypeAdapterFactory 将未处理的工作正确委托给 delegateAdapter

可能需要序列化为 null 的对象实现此接口

/**
 * [JsonNullableTypeAdapterFactory] needs to be registered with the [com.google.gson.Gson]
 * serializing implementations of [JsonNullable] for [JsonNullable] to work.
 *
 * [JsonNullable] allows objects to choose at runtime whether they should be serialized as "null"
 * serialized normally, or be omitted from the JSON output from [com.google.gson.Gson].
 *
 * when [isJsonNull] returns true, the subclass will be serialized to a [com.google.gson.JsonNull].
 *
 * when [isJsonNull] returns false, the subclass will be serialized normally.
 */
interface JsonNullable {

    /**
     * return true to have the entire object serialized as `null` during JSON serialization.
     * return false to have this object serialized normally.
     */
    fun isJsonNull(): Boolean
}

类型适配器工厂将值序列化为null

class JsonNullableTypeAdapterFactory : TypeAdapterFactory {
    override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
        return object : TypeAdapter<T>() {
            private val delegateAdapter = gson.getDelegateAdapter(this@JsonNullableTypeAdapterFactory, type)
            override fun read(reader: JsonReader): T = delegateAdapter.read(reader)
            override fun write(writer: JsonWriter, value: T?) {
                if (value is JsonNullable && value.isJsonNull()) {
                    val originalSerializeNulls = writer.serializeNulls
                    writer.serializeNulls = true
                    writer.nullValue()
                    writer.serializeNulls = originalSerializeNulls
                } else {
                    delegateAdapter.write(writer, value)
                }
            }
        }
    }
}

使用GSON注册类型适配器工厂

new GsonBuilder()
    // ....
    .registerTypeAdapterFactory(new JsonNullableTypeAdapterFactory())
    // ....
    .create();

被序列化为JSON的示例对象

data class Parent(
    val hello: Child?,
    val world: Child?
)

data class Child(
    val name: String?
) : JsonNullable {
    override fun isJsonNull(): Boolean = name == null
}

0
创建com.google.gson.TypeAdapter的子类,并使用注释com.google.gson.annotations.JsonAdapter将其注册到所需字段。或者使用GsonBuilder.registerTypeAdapter进行注册。在适配器中应实现write(和read)。例如:
public class JsonTestNullableAdapter extends TypeAdapter<Test> {

    @Override
    public void write(JsonWriter out, Test value) throws IOException {
        out.beginObject();
        out.name("name");
        out.value(value.name);
        out.name("value");
        if (value.value == null) {
            out.setSerializeNulls(true);
            out.nullValue();
            out.setSerializeNulls(false);
        } else {
            out.value(value.value);
        }
        out.endObject();
    }

    @Override
    public Test read(JsonReader in) throws IOException {
        in.beginObject();
        Test result = new Test();
        in.nextName();
        if (in.peek() != NULL) {
            result.name = in.nextString();
        } else {
            in.nextNull();
        }
        in.nextName();
        if (in.peek() != NULL) {
            result.value = in.nextString();
        } else {
            in.nextNull();
        }
        in.endObject();
        return result;
    }

}

MainClass中,为Test类字段添加带有适配器的JsonAdapter注释:
public static class MClass {
    public String id;
    public String name;
    @JsonAdapter(JsonTestNullableAdapter.class)
    public Test test;
}

System.out.println(new Gson.toJson(mainClass)) 的输出为:

{
    "id": "101",
    "test": {
        "name": "testName",
        "value": null
    }
}

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