【问题标题】:Gson serialize null for specific class or field特定类或字段的 Gson 序列化 null
【发布时间】:2016-05-30 08:54:30
【问题描述】:

我想序列化特定字段或类的空值。

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

如何达到想要的输出?

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

  • 默认序列化空值,
  • 序列化具有特定注释的字段的空值。

【问题讨论】:

标签: java json gson


【解决方案1】:

我有一个类似于 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
}

【讨论】:

  • 谢谢!我在一个 Java 项目中使用 Retrofit 重新实现了它,这是我为 GSON 中可自定义的可空类型找到的最佳解决方案。请注意,至少在 Java 中,您需要从 declaredField.getAnnotation(SerializedName.class).value() 中提取序列化的字段名称,以便它适用于更复杂的变量名称。
  • 有趣的@Arvoreniad,我已经用 Kotlin 实现测试了这个案例,不需要额外的逻辑来保留序列化的名称。 delegateAdapter 应该确保这一点。
  • 我在 Android 上使用 Kotlin,我注意到 @Arvoreniad 所做的事情。带有@SerializedName 注解的字段,如果为 null,则在生成的 JSON 中被序列化为 null。
  • @Arvoreniad 你能发布你的 Java 解决方案吗?
  • @Zookey 我刚刚发布了解决方案作为附加答案。
【解决方案2】:

我有接口来检查对象何时应序列化为空:

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 值本身不能为空,否则自定义适配器注解将被忽略。

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

【讨论】:

  • 这个解决方案的性能相当糟糕,因为它创建了一个JsonElement树作为中间数据结构,而不是直接写入JsonWriter。
  • @MiroSpönemann 如果我没记错的话,objectAdapter.toJsonTree 用于将对象序列化为普通 json 时使用。
  • toJsonTree() 用于创建 JsonElements 树。在不创建中间数据结构的情况下直接写入 JsonWriter 会快得多。执行此操作的通用 Gson 类是 ReflectiveTypeAdapterFactory。
  • 您可以尝试编写一个 TypeAdapterFactory 并使用gson.getDelegateAdapter(...) 获取正确的类型适配器;有关详细信息,请参阅该方法的 JavaDoc。
【解决方案3】:

对于那些寻找 @Joris 出色答案的 Java 版本的人来说,下面的代码应该可以解决问题。它在很大程度上只是 Kotlin 的翻译,对如何获取属性的序列化名称进行了微小的改进,以确保它在序列化名称与属性名称不同时始终有效(请参阅原始答案中的 cmets)。

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

                out.setSerializeNulls(true);
                elementAdapter.write(out, jsonObject);
            }

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

但是,如果愿意,另一种方法应该同样适用于此代码。

【讨论】:

    【解决方案4】:

    创建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 中将带有适配器的JsonAdapter 注释添加到Test 类字段:

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

    【讨论】:

      【解决方案5】:

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

      这个实现:

      • 让您在运行时选择 JSON 是否为
          • 发生在JsonNullable.isJsonNull() == true
        • 不为空
          • 发生在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 注册 thr 类型适配器工厂

      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
      }
      

      【讨论】:

        【解决方案6】:

        添加到@Arvoreniad 给出的答案

        两个新增功能是在输出设置为 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);
                    }
                };
            }
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-12-30
          • 1970-01-01
          • 2012-07-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2022-12-11
          相关资源
          最近更新 更多