让Gson在错误的类型上抛出exception

我在我的项目中使用Gson将JSON-Strings反序列化为Java-Objects。 如果我发出请求,我希望服务器有明确定义的响应。 服务器将返回我期望的明确定义的响应,或者它将返回一个(也定义的)错误对象。

为了清楚起见:假设我有一个像这样的简单对象:

class Dummy{ private String foo; private int bar; } 

和像这样的错误对象:

 class ErrorHolder{ private RequestError error; } class RequestError{ private String publicMsg; private String msg; } 

如果我得到像服务器响应

{"foo":"Hello World", "bar":3 }

一切都按预期工作。

但如果回应是这样的话

{"error":{"publicMsg":"Something bad happened", msg:"you forgot requesting some parameter"}}

我会得到一个Dummy对象,其中foonullbar为0! Gson文档(来自Json)明确指出:

抛出JsonSyntaxException – 如果json不是classOfT类型的对象的有效表示

所以如果我尝试解析第二个响应,我希望得到一个JsonSyntaxException:

 Dummy dummy = Gson.fromJson(secondResponse, Dummy.class); 

因为Json不代表Dummy对象,而是ErrorHolder对象。

所以我的问题是:有没有办法,Gson以某种方式检测到错误的类型,并抛出一个exception?

不幸的是,文档在那里有点误导。

如果你的类的字段类型与JSON中的字段不匹配,它只会抛出exception,即使这样,它也会尝试修复它(将JSON中的int转换为类中的String例如)。 如果你的POJO中有类似Date字段的东西,并且它在JSON中遇到了一个int ,它会抛出它。 静默忽略JSON中但不存在于POJO中的字段,JSON中缺少但存在于POJO中的字段设置为null

目前,GSON没有为任何类型的“严格”反序列化提供机制,您可以在POJO中为字段添加类似@Required注释。

在你的情况下……我只是扩展我的POJO以包含一个内部错误对象…类似于:

 class Dummy { private String foo; private int bar; private Error error; private class Error { String publicMsg; String msg; } public boolean isError() { return error != null; } // setters and getters for your data, the error msg, etc. } 

您的另一个选择是编写一个自定义反序列化器,如果JSON是错误,则抛出exception,例如:

 class MyDeserializer implements JsonDeserializer { @Override public Dummy deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { JsonObject jsonObject = (JsonObject) json; if (jsonObject.get("error") != null) { throw new JsonParseException("Error!"); } return new Gson().fromJson(json, Dummy.class); } } 

编辑添加:最近有人赞成这个并重新阅读它我想“嗯,你知道,你可以自己做这个,它可能很方便”。

这是一个可重复使用的反序列化器和注释,它将完全符合OP的要求。 限制是如果POJO原样需要自定义反序列化器,则必须更进一步,并在构造函数中传入Gson对象以反序列化为对象本身或将注释检出移动到单独的方法中在你的反序列化器中使用它。 您还可以通过创建自己的exception并将其传递给JsonParseException来改进exception处理,以便可以通过调用JsonParseException getCause()检测它。

这一切都说,在绝大多数情况下,这将有效:

 public class App { public static void main(String[] args) { Gson gson = new GsonBuilder() .registerTypeAdapter(TestAnnotationBean.class, new AnnotatedDeserializer()) .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 implements JsonDeserializer { 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; } } 

输出:

这是foo
这是酒吧
这是foo
空值
线程“main”com.google.gson.JsonParseException中的exception:JSON中缺少字段:foo

我创建了Brian的解决方案的更新版本,该解决方案处理嵌套对象并进行了其他一些小的更改。 该代码还包括一个更简单的构建器,用于创建Gson对象,这些对象知道带有使用JsonRequired注释的字段的类。

 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Field; import java.lang.reflect.Type; import java.util.List; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.collect.FluentIterable; import com.google.common.collect.Lists; import com.google.common.primitives.Primitives; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; import com.google.gson.JsonElement; import com.google.gson.JsonParseException; public class AnnotatedDeserializer implements JsonDeserializer { private final Gson gson = new Gson(); public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException { T target = gson.fromJson(je, type); checkRequired(target); return target; } private List findMissingFields(Object target, List invalidFields) { for (Field field : target.getClass().getDeclaredFields()) { if (field.getAnnotation(JsonRequired.class) != null) { Object fieldValue = ReflectionUtil.getFieldValue(target, field); if (fieldValue == null) { invalidFields.add(field); continue; } if (!isPrimitive(fieldValue)) { findMissingFields(fieldValue, invalidFields); } } } return invalidFields; } private void checkRequired(Object target) { List invalidFields = Lists.newArrayList(); findMissingFields(target, invalidFields); if (!invalidFields.isEmpty()) { throw new JsonParseException("Missing JSON required fields: {" + FluentIterable.from(invalidFields).transform(toMessage).join(Joiner.on(", ")) + "}"); } } static Function toMessage = new Function() { @Override public String apply(Field field) { return field.getDeclaringClass().getName() + "/" + field.getName(); } }; private boolean isPrimitive(Object target) { for (Class primitiveClass : Primitives.allPrimitiveTypes()) { if (primitiveClass.equals(target.getClass())) { return true; } } return false; } public static class RequiredFieldAwareGsonBuilder { private GsonBuilder gsonBuilder; private RequiredFieldAwareGsonBuilder(GsonBuilder gsonBuilder) { this.gsonBuilder = gsonBuilder; } public static RequiredFieldAwareGsonBuilder builder() { return new RequiredFieldAwareGsonBuilder(new GsonBuilder()); } public  RequiredFieldAwareGsonBuilder withRequiredFieldAwareType(Class classOfT) { gsonBuilder.registerTypeAdapter(classOfT, new AnnotatedDeserializer()); return this; } public Gson build() { return gsonBuilder.create(); } } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public static @interface JsonRequired { } } 

和reflection实用程序

 import java.lang.reflect.Field; public final class ReflectionUtil { private ReflectionUtil() { } public static Object getFieldValue(Object target, Field field) { try { boolean originalFlag = changeAccessibleFlag(field); Object fieldValue = field.get(target); restoreAccessibleFlag(field, originalFlag); return fieldValue; } catch (IllegalAccessException e) { throw new RuntimeException("Failed to access field " + field.getDeclaringClass().getName() + "/" + field.getName(), e); } } private static void restoreAccessibleFlag(Field field, boolean flag) { field.setAccessible(flag); } private static boolean changeAccessibleFlag(Field field) { boolean flag = field.isAccessible(); field.setAccessible(true); return flag; } } 

如果你使用Guice,你可以在你的模块中添加这样的东西来注入Gson对象

 @Provides @Singleton static Gson provideGson() { return RequiredFieldAwareGsonBuilder.builder().withRequiredFieldAwareType(MyType1.class) .withRequiredFieldAwareType(MyType2.class).build(); } 

我不是所选解决方案的粉丝。 它有效,但它不是使用Gson的方式。 Gson将特定的JSON模式映射到对象,反之亦然。 理想情况下,您正在使用的JSON格式正确(因此,如果您可以控制JSON格式,请考虑更改它),但如果没有,则应设计解析对象以处理您希望接收的所有情况。

有时您需要编写自定义的JsonDeserializer ,但这不是其中之一。 发送消息错误是一种非常标准的做法,使用正确的数据结构,GSON可以直接处理这样一个简单的用例。

如果您可以控制JSON模式

考虑这样的事情:

 { "message": { "foo": "Hello World", "bar": 3 }, "error": null; } { "message": null, "error": { "publicMsg": "Something bad happened", "msg": "you forgot requesting some parameter" } } 

请注意,您现在可以定义一个干净的包装类,在可能的情况下提供 Dummy对象:

 public class JsonResponse { private Dummy message; private RequestError error; public boolean hasError() { return error != null; } public Dummy getDummy() { Preconditions.checkState(!hasError()); return message; } public RequestError getError() { Preconditions.checkState(hasError()); return error; } } 

如果必须处理现有的JSON模式

如果您无法重构架构,则必须重新构建解析类,它看起来像这样:

 public class JsonResponse { private String foo; private int bar; private RequestError error; public boolean hasError() { return error != null; } public Dummy getDummy() { Preconditions.checkState(!hasError()); return new Dummy(foo, bar); } public RequestError getError() { Preconditions.checkState(hasError()); return error; } } 

这比修复模式更不可取,但是你可以采用相同的通用API – 调用hasError()来查看请求是否成功,然后根据需要调用getDummy()getError() 。 调用其他方法(例如,收到错误时的getDummy() )将失败快速。