Gson 可选字段和必填字段

如何处理 Gson和必填字段与可选字段?

因为所有字段都是可选的,所以我不能根据响应 json 是否包含某个键来判断网络请求是否失败,Gson会简单地将其解析为 null。

方法我正在使用 gson.fromJson(json, mClassOfT);

例如,如果我有以下 json:

{"user_id":128591, "user_name":"TestUser"}

还有我的班级:

public class User {


@SerializedName("user_id")
private String mId;


@SerializedName("user_name")
private String mName;


public String getId() {
return mId;
}


public void setId(String id) {
mId = id;
}


public String getName() {
return mName;
}


public void setName(String name) {
mName = name;
}
}

如果 json 不包含 user_iduser_name键,是否可以选择让 Gson失败?

在许多情况下,您可能至少需要解析一些值,而其他值可能是可选的?

是否有任何模式或库用于全局处理这种情况?

谢谢。

93300 次浏览

正如您所注意到的,Gson 没有定义“必填字段”的工具,如果 JSON 中缺少某些内容,那么您只能在反序列化的对象中获得 null

下面是一个可重用的反序列化器和注释,它可以做到这一点。限制在于,如果 POJO 需要一个自定义反序列化器,那么您必须更进一步,要么在构造函数中传入一个 Gson对象来反序列化对象本身,要么将检出的注释移动到一个单独的方法中,并在反序列化器中使用它。您还可以通过创建自己的异常并将其传递给 JsonParseException来改进异常处理,这样就可以通过调用方中的 getCause()检测到异常。

尽管如此,在绝大多数情况下,这种方法都会奏效:

public class App
{


public static void main(String[] args)
{
Gson gson =
new GsonBuilder()
.registerTypeAdapter(TestAnnotationBean.class, new AnnotatedDeserializer<TestAnnotationBean>())
.create();


String json = "{\"foo\":\"This is foo\",\"bar\":\"this is bar\"}";
TestAnnotationBean tab = gson.fromJson(json, TestAnnotationBean.class);
System.out.println(tab.foo);
System.out.println(tab.bar);


json = "{\"foo\":\"This is foo\"}";
tab = gson.fromJson(json, TestAnnotationBean.class);
System.out.println(tab.foo);
System.out.println(tab.bar);


json = "{\"bar\":\"This is bar\"}";
tab = gson.fromJson(json, TestAnnotationBean.class);
System.out.println(tab.foo);
System.out.println(tab.bar);
}
}


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


class TestAnnotationBean
{
@JsonRequired public String foo;
public String bar;
}


class AnnotatedDeserializer<T> implements JsonDeserializer<T>
{


public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException
{
T pojo = new Gson().fromJson(je, type);


Field[] fields = pojo.getClass().getDeclaredFields();
for (Field f : fields)
{
if (f.getAnnotation(JsonRequired.class) != null)
{
try
{
f.setAccessible(true);
if (f.get(pojo) == null)
{
throw new JsonParseException("Missing field in JSON: " + f.getName());
}
}
catch (IllegalArgumentException ex)
{
Logger.getLogger(AnnotatedDeserializer.class.getName()).log(Level.SEVERE, null, ex);
}
catch (IllegalAccessException ex)
{
Logger.getLogger(AnnotatedDeserializer.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
return pojo;


}
}

产出:

This is foo
this is bar
This is foo
null
Exception in thread "main" com.google.gson.JsonParseException: Missing field in JSON: foo

这是我的简单解决方案,它创建了一个具有最少代码的通用解决方案。

  1. 创建@可选注释
  2. 标记第一个可选项。假设休息是可选的。假设前面的是必需的。
  3. 创建一个通用的‘ loader’方法来检查源 Json 对象是否有值。一旦遇到@Options 字段,循环将停止。

我使用子类化,所以繁重的工作是在超类中完成的。

下面是超类代码。

import com.google.gson.Gson;
import java.lang.reflect.Field;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
...
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Optional {
public boolean enabled() default true;
}

和粗重的工作方法

@SuppressWarnings ("unchecked")
public <T> T payload(JsonObject oJR,Class<T> T) throws Exception {
StringBuilder oSB = new StringBuilder();
String sSep = "";
Object o = gson.fromJson(oJR,T);
// Ensure all fields are populated until we reach @Optional
Field[] oFlds =  T.getDeclaredFields();
for(Field oFld:oFlds) {
Annotation oAnno = oFld.getAnnotation(Optional.class);
if (oAnno != null) break;
if (!oJR.has(oFld.getName())) {
oSB.append(sSep+oFld.getName());
sSep = ",";
}
}
if (oSB.length() > 0) throw CVT.e("Required fields "+oSB+" mising");
return (T)o;
}

和一个用法的例子

public static class Payload {
String sUserType ;
String sUserID   ;
String sSecpw    ;
@Optional
String sUserDev  ;
String sUserMark ;
}

和人口密码

Payload oPL = payload(oJR,Payload.class);

在这种情况下,sUserDev 和 sUserMark 是可选的,其余的都是必需的。该解决方案依赖于这样一个事实,即该类以声明的顺序存储 Field 定义。

我搜索了很多,没有找到好的答案。我选择的解决方案如下:

我需要从 JSON 中设置的每个字段都是一个对象,例如,装箱的 Integer、 Boolean 等。然后,使用反射,我可以检查该字段是否为空:

public class CJSONSerializable {
public void checkDeserialization() throws IllegalAccessException, JsonParseException {
for (Field f : getClass().getDeclaredFields()) {
if (f.get(this) == null) {
throw new JsonParseException("Field " + f.getName() + " was not initialized.");
}
}
}
}

从这个类中,我可以派生出我的 JSON 对象:

public class CJSONResp extends CJSONSerializable {
@SerializedName("Status")
public String status;


@SerializedName("Content-Type")
public String contentType;
}

然后在使用 GSON 进行解析之后,我可以调用 checkSerialization,如果某些字段为 null,它将报告我。

布莱恩 · 罗奇的回答非常好,但有时也需要处理:

  • 模型的超类的性质
  • 数组内部的属性

为此,可以使用以下类:

/**
* Adds the feature to use required fields in models.
*
* @param <T> Model to parse to.
*/
public class JsonDeserializerWithOptions<T> implements JsonDeserializer<T> {


/**
* To mark required fields of the model:
* json parsing will be failed if these fields won't be provided.
* */
@Retention(RetentionPolicy.RUNTIME) // to make reading of this field possible at the runtime
@Target(ElementType.FIELD)          // to make annotation accessible through reflection
public @interface FieldRequired {}


/**
* Called when the model is being parsed.
*
* @param je   Source json string.
* @param type Object's model.
* @param jdc  Unused in this case.
*
* @return Parsed object.
*
* @throws JsonParseException When parsing is impossible.
* */
@Override
public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
throws JsonParseException {
// Parsing object as usual.
T pojo = new Gson().fromJson(je, type);


// Getting all fields of the class and checking if all required ones were provided.
checkRequiredFields(pojo.getClass().getDeclaredFields(), pojo);


// Checking if all required fields of parent classes were provided.
checkSuperClasses(pojo);


// All checks are ok.
return pojo;
}


/**
* Checks whether all required fields were provided in the class.
*
* @param fields Fields to be checked.
* @param pojo   Instance to check fields in.
*
* @throws JsonParseException When some required field was not met.
* */
private void checkRequiredFields(@NonNull Field[] fields, @NonNull Object pojo)
throws JsonParseException {
// Checking nested list items too.
if (pojo instanceof List) {
final List pojoList = (List) pojo;
for (final Object pojoListPojo : pojoList) {
checkRequiredFields(pojoListPojo.getClass().getDeclaredFields(), pojoListPojo);
checkSuperClasses(pojoListPojo);
}
}


for (Field f : fields) {
// If some field has required annotation.
if (f.getAnnotation(FieldRequired.class) != null) {
try {
// Trying to read this field's value and check that it truly has value.
f.setAccessible(true);
Object fieldObject = f.get(pojo);
if (fieldObject == null) {
// Required value is null - throwing error.
throw new JsonParseException(String.format("%1$s -> %2$s",
pojo.getClass().getSimpleName(),
f.getName()));
} else {
checkRequiredFields(fieldObject.getClass().getDeclaredFields(), fieldObject);
checkSuperClasses(fieldObject);
}
}


// Exceptions while reflection.
catch (IllegalArgumentException | IllegalAccessException e) {
throw new JsonParseException(e);
}
}
}
}


/**
* Checks whether all super classes have all required fields.
*
* @param pojo Object to check required fields in its superclasses.
*
* @throws JsonParseException When some required field was not met.
* */
private void checkSuperClasses(@NonNull Object pojo) throws JsonParseException {
Class<?> superclass = pojo.getClass();
while ((superclass = superclass.getSuperclass()) != null) {
checkRequiredFields(superclass.getDeclaredFields(), pojo);
}
}


}

首先描述了用于标记必填字段的接口(注释) ,稍后我们将看到它的用法示例:

    /**
* To mark required fields of the model:
* json parsing will be failed if these fields won't be provided.
* */
@Retention(RetentionPolicy.RUNTIME) // to make reading of this field possible at the runtime
@Target(ElementType.FIELD)          // to make annotation accessible throw the reflection
public @interface FieldRequired {}

然后实现 deserialize方法,它像往常一样解析 json 字符串: 结果 pojo中缺少的属性将具有 null值:

T pojo = new Gson().fromJson(je, type);

然后启动对解析后的 pojo的所有字段的递归检查:

checkRequiredFields(pojo.getClass().getDeclaredFields(), pojo);

然后我们还检查了 pojo超级类的所有字段:

checkSuperClasses(pojo);

当一些 SimpleModel扩展它的 SimpleParentModel时,它是必需的,我们希望确保标记为所需的 SimpleModel的所有属性都作为 SimpleParentModel的属性提供。

让我们来看看 checkRequiredFields方法。首先,它检查某些属性是否是 List(json array)的实例——在这种情况下,还应该检查列表的所有对象,以确保它们也提供了所有必需的字段:

if (pojo instanceof List) {
final List pojoList = (List) pojo;
for (final Object pojoListPojo : pojoList) {
checkRequiredFields(pojoListPojo.getClass().getDeclaredFields(), pojoListPojo);
checkSuperClasses(pojoListPojo);
}
}

然后,我们遍历 pojo的所有字段,检查是否提供了带有 FieldRequired注释的所有字段(这意味着这些字段不为空)。如果我们遇到一些空属性,这是必需的-一个异常将被激发。否则,将为当前字段启动另一个递归验证步骤,并且还将检查该字段的父类的属性:

        for (Field f : fields) {
// If some field has required annotation.
if (f.getAnnotation(FieldRequired.class) != null) {
try {
// Trying to read this field's value and check that it truly has value.
f.setAccessible(true);
Object fieldObject = f.get(pojo);
if (fieldObject == null) {
// Required value is null - throwing error.
throw new JsonParseException(String.format("%1$s -> %2$s",
pojo.getClass().getSimpleName(),
f.getName()));
} else {
checkRequiredFields(fieldObject.getClass().getDeclaredFields(), fieldObject);
checkSuperClasses(fieldObject);
}
}


// Exceptions while reflection.
catch (IllegalArgumentException | IllegalAccessException e) {
throw new JsonParseException(e);
}
}
}

最后一个方法是 checkSuperClasses: 它只是运行 pojo超类的类似的必需字段验证检查属性:

    Class<?> superclass = pojo.getClass();
while ((superclass = superclass.getSuperclass()) != null) {
checkRequiredFields(superclass.getDeclaredFields(), pojo);
}

最后,让我们回顾一些 JsonDeserializerWithOptions使用的例子。假设我们有以下模型:

private class SimpleModel extends SimpleParentModel {


@JsonDeserializerWithOptions.FieldRequired Long id;
@JsonDeserializerWithOptions.FieldRequired NestedModel nested;
@JsonDeserializerWithOptions.FieldRequired ArrayList<ListModel> list;


}


private class SimpleParentModel {


@JsonDeserializerWithOptions.FieldRequired Integer rev;


}


private class NestedModel extends NestedParentModel {


@JsonDeserializerWithOptions.FieldRequired Long id;


}


private class NestedParentModel {


@JsonDeserializerWithOptions.FieldRequired Integer rev;


}


private class ListModel {


@JsonDeserializerWithOptions.FieldRequired Long id;


}

我们可以确信,SimpleModel将以这种方式正确解析,没有例外:

final Gson gson = new GsonBuilder()
.registerTypeAdapter(SimpleModel.class, new JsonDeserializerWithOptions<SimpleModel>())
.create();


gson.fromJson("{\"list\":[ { \"id\":1 } ], \"id\":1, \"rev\":22, \"nested\": { \"id\":2, \"rev\":2 }}", SimpleModel.class);

当然,提供的解决方案可以得到改进,并接受更多的特性: 例如,没有用 FieldRequired注释标记的嵌套对象的验证。目前它超出了回答的范围,但可以在以后添加。

(灵感来自 Brian Roache 的回答。)

Brian 的答案似乎不适用于原语,因为这些值可以初始化为 null 以外的值(例如 0)。

而且,似乎每种类型都必须注册反序列化器。更具可伸缩性的解决方案使用 TypeAdapterFactory(如下所示)。

在某些情况下,来自所需字段的白名单异常(即 JsonOptional字段)比根据需要对所有字段进行注释更安全。

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

尽管这种方法可以很容易地适用于需要的领域。

import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.Streams;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;


import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;


public class AnnotatedTypeAdapterFactory implements TypeAdapterFactory {
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
Class<? super T> rawType = typeToken.getRawType();


Set<Field> requiredFields = Stream.of(rawType.getDeclaredFields())
.filter(f -> f.getAnnotation(JsonOptional.class) == null)
.collect(Collectors.toSet());


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


final TypeAdapter<T> baseAdapter = (TypeAdapter<T>) gson.getAdapter(rawType);


return new TypeAdapter<T>() {


@Override
public void write(JsonWriter jsonWriter, T o) throws IOException {
baseAdapter.write(jsonWriter, o);
}


@Override
public T read(JsonReader in) throws IOException {
JsonElement jsonElement = Streams.parse(in);


if (jsonElement.isJsonObject()) {
ArrayList<String> missingFields = new ArrayList<>();
for (Field field : requiredFields) {
if (!jsonElement.getAsJsonObject().has(field.getName())) {
missingFields.add(field.getName());
}
}
if (!missingFields.isEmpty()) {
throw new JsonParseException(
String.format("Missing required fields %s for %s",
missingFields, rawType.getName()));
}
}


TypeAdapter<T> delegate = gson.getDelegateAdapter(AnnotatedTypeAdapterFactory.this, typeToken);
return delegate.fromJsonTree(jsonElement);


}
};


}
}