我有json模型:
{"field1":"0", "field2": "1"}
有时field1
可能是null
,或者可能会丢失:
{"field1": null, "field2": "1"},
{"field2": "1"}
我可以区分字段是否为空或缺少字段?
我不想为完整模型编写自定义反序列化器,因为真正的json对象很复杂。
答案 0 :(得分:0)
您可以识别特定键是否缺失或为空,但是您无法将其解析为Java对象。您必须在Java模型中将该键定义为JsonElement。
public class Pojo {
private JsonElement field1;
private String field2;
}
然后在Gson完成解析json之后,你可以检查那个特定的对象(field1)是否是:
您可以将所有这些逻辑包装到辅助类中。
答案 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]
您可以使用此方法通过编写自定义类型适配器来检测“不完整”的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的类,但它允许空值。