简体   繁体   English

特定类或字段的 Gson 序列化 null

[英]Gson serialize null for specific class or field

I want to serialize nulls for a specific field or class.我想序列化特定字段或类的空值。

In GSON, the option serializeNulls() applies to the whole JSON.在 GSON 中,选项serializeNulls()适用于整个 JSON。

Example:例子:

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;    

Creating JSON using GSON:使用 GSON 创建 JSON:

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

Current ouput:当前输出:

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

Desired output:期望的输出:

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

How to achieve the desired output?如何达到预期的输出?

Preferred solution would have the following properties:首选解决方案将具有以下属性:

  • Do NOT serialize nulls by default,默认情况下不要序列化空值,
  • Serialize nulls for fields with a specific annotation.序列化具有特定注释的字段的空值。

I have a solution similar to the one of Aleksey but that can be applied to one or more fields in any class (example in Kotlin):我有一个类似于 Aleksey 的解决方案,但它可以应用于任何类中的一个或多个字段(Kotlin 中的示例):

Create a new annotation for fields that should be serialized as null:为应序列化为 null 的字段创建新注释:

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

Create a TypeAdapterFactory that checks if a class has fields annotated with this annotation and removes the fields that are null and not annotated with the annotation from the JsonTree when writing the object:创建一个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)
            }
        }
    }
}

Register the adapter with your Gson instance:向您的 Gson 实例注册适配器:

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

And annotate the fields you would like to be nullable:并注释您希望可以为空的字段:

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

Serialization result:序列化结果:

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

json: json:

{
    "name": null
}

I have interface to check when object should be serialized as null:我有接口来检查对象何时应序列化为空:

public interface JsonNullable {
  boolean isJsonNull();
}

And the corresponding TypeAdapter (supports write only)以及对应的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;
  }
}

Use it as follows:按如下方式使用它:

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

In the class where Foo value should be serialized as null.在 Foo 值应序列化为 null 的类中。 Note that foo value itself must be not null, otherwise custom adapter annotation will be ignored.注意 foo 值本身不能为空,否则自定义适配器注解将被忽略。

public class Bar {
  @JsonAdapter(JsonNullableAdapter.class)
  public Foo foo = new Foo();
}

For those looking for a Java version of @Joris's excellent answer, the below code should do the trick.对于那些寻找 @Joris 出色答案的 Java 版本的人来说,下面的代码应该可以解决问题。 It's largely just a translation of the Kotlin, with a minor improvement to how the serialized name of the attribute is fetched to ensure it always works when the serialized name is different than the attribute name (see the comments on the original answer).它在很大程度上只是 Kotlin 的翻译,对如何获取属性的序列化名称进行了微小的改进,以确保它在序列化名称与属性名称不同时始终有效(请参阅原始答案的注释)。

This is the TypeAdapterFactory implementation:这是TypeAdapterFactory的实现:

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);
            }

        };
    }
}

And this is the @JsonNullable annotation to mark the target attributes:这是标记目标属性的@JsonNullable注解:

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

I implemented it as an @JsonAdapter(NullableAdapterFactory.class) annotation on the object class, rather registering it as a TypeAdapterFactory on the GsonBuilder instance, so my object classes looked a bit like this:我将它实现为对象类上的@JsonAdapter(NullableAdapterFactory.class)注释,而不是将其注册为TypeAdapterFactory实例上的GsonBuilder ,所以我的对象类看起来有点像这样:

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

However, the other approach should work just as well with this code if preferred.但是,如果愿意,另一种方法应该同样适用于此代码。

Create subclass of com.google.gson.TypeAdapter and register it for required field using annotation com.google.gson.annotations.JsonAdapter .创建com.google.gson.TypeAdapter的子类并使用注释com.google.gson.annotations.JsonAdapter将其注册为必填字段。 Or register it using GsonBuilder.registerTypeAdapter .或者使用GsonBuilder.registerTypeAdapter注册它。 In that adapter write (and read ) should be implemented.在那个适配器中write (和read )应该被实现。 For example:例如:

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

}

in MainClass add JsonAdapter annotation with the adapter to Test class field:MainClass中将带有适配器的JsonAdapter注释添加到Test类字段:

public static class MClass {
    public String id;
    public String name;
    @JsonAdapter(JsonTestNullableAdapter.class)
    public Test test;
}

the output of System.out.println(new Gson.toJson(mainClass)) is: System.out.println(new Gson.toJson(mainClass))的输出是:

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

I took a few ideas from various answers here.我从这里的各种答案中获得了一些想法。

this implementation:这个实现:

  • lets you choose at runtime , whether the JSON is让您在运行时选择 JSON 是否为
    • null无效的
      • happens when JsonNullable.isJsonNull() == trueJsonNullable.isJsonNull() == true时发生
    • not null不为空
      • happens when JsonNullable.isJsonNull() == falseJsonNullable.isJsonNull() == false时发生
    • omitted from the JSON (useful for HTTP PATCH requests)从 JSON 中省略(对 HTTP PATCH 请求有用)
      • happens field in Parent containing JsonNullable is null包含JsonNullableParent中发生的字段为null
  • no annotations needed不需要注释
  • properly delegates unhandled work to a delegateAdapter by using a TypeAdapterFactory使用TypeAdapterFactory将未处理的工作正确委托给delegateAdapter

objects that may need to be serialized to null implement this interface可能需要序列化为 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
}

type adapter factory that serializes values to null将值序列化为 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)
                }
            }
        }
    }
}

register thr type adapter factroy with GSON向 GSON 注册 thr 类型适配器工厂

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

example object that gets serialized to JSON序列化为 JSON 的示例对象

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

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

Adding to the answer given by @Arvoreniad添加到@Arvoreniad 给出的答案

The two additions are resetting the null serialization state in the JsonWriter after setting to true for the output and to use the field naming policy from Gson for getting the field name.这两个添加是在将输出设置为 true 后重置 JsonWriter 中的空序列化状态,并使用 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);
            }
        };
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM