GSON。区分空值字段和缺失字段

时间:2018-01-26 16:02:06

标签: android json null gson deserialization

我有json模型:

{"field1":"0", "field2": "1"}

有时field1可能是null,或者可能会丢失:

{"field1": null, "field2": "1"},
{"field2": "1"}

我可以区分字段是否为空或缺少字段?

我不想为完整模型编写自定义反序列化器,因为真正的json对象很复杂。

3 个答案:

答案 0 :(得分:0)

您可以识别特定键是否缺失或为空,但是您无法将其解析为Java对象。您必须在Java模型中将该键定义为JsonElement

public class Pojo {
    private JsonElement field1;
    private String field2;
}

然后在Gson完成解析json之后,你可以检查那个特定的对象(field1)是否是:

  1. null - 这表示缺少密钥
  2. JsonNull的实例 - 这意味着键的值为空
  3. 否则 - 这表示该键具有非空值。然后,您可以使用Gson将其再次单独解析为Java对象。
  4. 您可以将所有这些逻辑包装到辅助类中。

答案 1 :(得分:0)

排序。 这里的主要问题是如何获得预期的对象属性。 您可以尝试自己检测预期的属性,但Gson可能提供了更好的但是黑客的方式(我不知道它是否可以在Gson的未来版本中使用)。 UnsafeAllocator类负责在不调用构造函数的情况下创建对象,并且由于Gson在Android上运行,因此它也适用于您。 这里非常好,因为我们可以找到使用这样的临时对象将其转换为JSON对象,然后获取其密钥。

private static final UnsafeAllocator unsafeAllocator = UnsafeAllocator.create();

/**
 * @param gson This Gson instance must have be initialized with {@link GsonBuilder#serializeNulls()}
 */
static Set<String> tryLookupKeys(final Gson gson, final Class<?> clazz)
        throws Exception {
    final Object o = unsafeAllocator.newInstance(clazz);
    final JsonElement jsonElement = gson.toJsonTree(o, clazz);
    if ( !jsonElement.isJsonObject() ) {
        throw new IllegalArgumentException(clazz + " cannot be converted to a JSON object");
    }
    return jsonElement.getAsJsonObject().keySet();
}

请注意,让传递的Gson实例序列化为空是至关重要的。 另一个注意事项是Gson在这里不是私有的,但是它应该被传递给方法以便尊重你的具体Gson实例密钥。

使用示例:

final class FooBarBaz {

    final String foo;
    final int bar;
    final String[] baz;

    FooBarBaz(final String foo, final int bar, final String[] baz) {
        this.foo = foo;
        this.bar = bar;
        this.baz = baz;
    }

}

private static final Gson gson = new GsonBuilder()
        .serializeNulls()
        .create();

final Set<String> expectedKeys = JsonProperties.tryLookupKeys(gson, FooBarBaz.class);
System.out.println("keys: " + expectedKeys);
System.out.println(Sets.difference(expectedKeys, gson.fromJson("{\"foo\":\"foo\",\"bar\":1,\"baz\":[]}", JsonObject.class).keySet()));
System.out.println(Sets.difference(expectedKeys, gson.fromJson("{\"foo\":\"foo\",\"bar\":1,\"baz\":null}", JsonObject.class).keySet()));
System.out.println(Sets.difference(expectedKeys, gson.fromJson("{\"foo\":\"foo\",\"bar\":1}", JsonObject.class).keySet()));

输出:

keys: [foo, bar, baz]
[]
[]
[baz]

第2部分

您可以使用此方法通过编写自定义类型适配器来检测“不完整”的JSON有效内容。 例如:

private static final Gson gson = new GsonBuilder()
        .serializeNulls()
        .registerTypeAdapterFactory(AllKeysRequiredTypeAdapterFactory.get())
        .create();
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface AllKeysRequired {
}
final class AllKeysRequiredTypeAdapterFactory
        implements TypeAdapterFactory {

    private static final TypeAdapterFactory allKeysRequiredTypeAdapterFactory = new AllKeysRequiredTypeAdapterFactory();

    private AllKeysRequiredTypeAdapterFactory() {
    }

    static TypeAdapterFactory get() {
        return allKeysRequiredTypeAdapterFactory;
    }

    @Override
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
        @SuppressWarnings("unchecked")
        final Class<T> rawType = (Class<T>) typeToken.getRawType();
        // Or any other way you would like to determine if the given class is fine to be validated
        if ( !rawType.isAnnotationPresent(AllKeysRequired.class) ) {
            return null;
        }
        final TypeAdapter<T> delegateTypeAdapter = gson.getDelegateAdapter(this, typeToken);
        final TypeAdapter<JsonElement> jsonElementTypeAdapter = gson.getAdapter(JsonElement.class);
        return AllKeysRequiredTypeAdapter.of(gson, rawType, delegateTypeAdapter, jsonElementTypeAdapter);
    }

    private static final class AllKeysRequiredTypeAdapter<T>
            extends TypeAdapter<T> {

        // This is for the cache below
        private final JsonPropertiesCacheKey jsonPropertiesCacheKey;
        private final TypeAdapter<T> delegateTypeAdapter;
        private final TypeAdapter<JsonElement> jsonElementTypeAdapter;

        private AllKeysRequiredTypeAdapter(final JsonPropertiesCacheKey jsonPropertiesCacheKey, final TypeAdapter<T> delegateTypeAdapter,
                final TypeAdapter<JsonElement> jsonElementTypeAdapter) {
            this.jsonPropertiesCacheKey = jsonPropertiesCacheKey;
            this.delegateTypeAdapter = delegateTypeAdapter;
            this.jsonElementTypeAdapter = jsonElementTypeAdapter;
        }

        private static <T> TypeAdapter<T> of(final Gson gson, final Class<?> rawType, final TypeAdapter<T> delegateTypeAdapter,
                final TypeAdapter<JsonElement> jsonElementTypeAdapter) {
            return new AllKeysRequiredTypeAdapter<>(new JsonPropertiesCacheKey(gson, rawType), delegateTypeAdapter, jsonElementTypeAdapter);
        }

        @Override
        public void write(final JsonWriter jsonWriter, final T t)
                throws IOException {
            delegateTypeAdapter.write(jsonWriter, t);
        }

        @Override
        public T read(final JsonReader jsonReader)
                throws IOException {
            try {
                // First, convert it to a tree to obtain its keys
                final JsonElement jsonElement = jsonElementTypeAdapter.read(jsonReader);
                // Then validate
                validate(jsonElement);
                // And if the validation passes, then just convert the tree to the object
                return delegateTypeAdapter.read(new JsonTreeReader(jsonElement));
            } catch ( final ExecutionException ex ) {
                throw new RuntimeException(ex);
            }
        }

        private void validate(final JsonElement jsonElement)
                throws ExecutionException {
            if ( !jsonElement.isJsonObject() ) {
                throw new JsonParseException("The given tree is not a JSON object");
            }
            final JsonObject jsonObject = jsonElement.getAsJsonObject();
            final Set<String> expectedProperties = jsonPropertiesCache.get(jsonPropertiesCacheKey);
            final Set<String> actualProperties = jsonObject.keySet();
            // This method comes from Guava but can be implemented using standard JDK
            final Set<String> difference = Sets.difference(expectedProperties, actualProperties);
            if ( !difference.isEmpty() ) {
                throw new JsonParseException("The given JSON object lacks some properties: " + difference);
            }
        }

    }

    private static final class JsonPropertiesCacheKey {

        private final Gson gson;
        private final Class<?> rawType;

        private JsonPropertiesCacheKey(final Gson gson, final Class<?> rawType) {
            this.gson = gson;
            this.rawType = rawType;
        }

        @Override
        @SuppressWarnings("ObjectEquality")
        public boolean equals(final Object o) {
            if ( this == o ) {
                return true;
            }
            if ( o == null || getClass() != o.getClass() ) {
                return false;
            }
            final JsonPropertiesCacheKey jsonPropertiesCacheKey = (JsonPropertiesCacheKey) o;
            @SuppressWarnings("ObjectEquality")
            final boolean areEqual = gson == jsonPropertiesCacheKey.gson && rawType == jsonPropertiesCacheKey.rawType;
            return areEqual;
        }

        @Override
        public int hashCode() {
            return gson.hashCode() * 31 + rawType.hashCode();
        }

    }

    private static final LoadingCache<JsonPropertiesCacheKey, Set<String>> jsonPropertiesCache = CacheBuilder.newBuilder()
            .maximumSize(50)
            .build(new CacheLoader<JsonPropertiesCacheKey, Set<String>>() {
                @Override
                public Set<String> load(final JsonPropertiesCacheKey jsonPropertiesCacheKey)
                        throws Exception {
                    return JsonProperties.tryLookupKeys(jsonPropertiesCacheKey.gson, jsonPropertiesCacheKey.rawType);
                }
            });

}

现在,如果我们应用类型适配器工厂,我们可以检查给定的JSON属性存在:

@AllKeysRequired
final class FooBarBaz {

    final String foo;
    final int bar;
    final String[] baz;

    FooBarBaz(final String foo, final int bar, final String[] baz) {
        this.foo = foo;
        this.bar = bar;
        this.baz = baz;
    }

}
private static final Gson gson = new GsonBuilder()
        .serializeNulls()
        .registerTypeAdapterFactory(AllKeysRequiredTypeAdapterFactory.get())
        .create();

gson.fromJson("{\"foo\":\"foo\",\"bar\":1,\"baz\":[]}", FooBarBaz.class);
gson.fromJson("{\"foo\":\"foo\",\"bar\":1,\"baz\":null}", FooBarBaz.class);
gson.fromJson("{\"foo\":\"foo\",\"bar\":1}", FooBarBaz.class);

最后一次gson.fromJson调用将抛出异常,并显示以下消息:

  

给定的JSON对象缺少一些属性:[baz]

答案 2 :(得分:0)

Gson首先将调用default / zero-arg构造函数,然后通过调用setter来使用Json值填充对象的字段。因此,如果默认构造函数设置的值不是null,则null值将指示该值明确为null。但是,这需要一个永远无法设置的默认值。

一种解决方案是创建POJO,其中所有字段均为java.util.Optional,并将字段初始化为null。每个获取器都会返回一个Optional<T>,但设置器只接受T

public class OptionalPOJO {
  private Optional<SomeType> someValue;

  public Optional<SomeType> getSomeValue() { return someValue; }
  public void setSomeValue(SomeType newVal) {
    this.someValue = Optional.ofNullable(newVal);
  }
}

public class POJO {
  private SomeType someValue;

  //normal getter and setter
}

因此,null表示缺少密钥,空Optional表示已明确提供了null,否则Optional将包含给定的值。

您还需要提供一个TypeAdapterFactory,用于解包Optional

这是对Optional的滥用。为了使其更加简洁,可以创建一个类似于Optional的类,但它允许空值。