java 让 Gson 在错误的类型上抛出异常
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/14242236/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
let Gson throw exceptions on wrong types
提问by Rafael T
I use Gson inside my projects to deserialize JSON-Strings to Java-Objects. If I do a request, I expect a well-defined response from the Server. The Server will either return the well-defined response I expect, OR it will return me an (also defined) error Object.
我在我的项目中使用 Gson 将 JSON 字符串反序列化为 Java 对象。如果我提出请求,我希望得到来自服务器的明确定义的响应。服务器要么返回我期望的明确定义的响应,要么返回一个(也定义的)错误对象。
To make things clear: suppose I have a simple Object like this:
澄清一下:假设我有一个像这样的简单对象:
class Dummy{
private String foo;
private int bar;
}
and an Error Object like this:
和一个像这样的错误对象:
class ErrorHolder{
private RequestError error;
}
class RequestError{
private String publicMsg;
private String msg;
}
If I get a Server-response like
如果我得到一个服务器响应
{"foo":"Hello World", "bar":3 }
{"foo":"Hello World", "bar":3 }
everything works as expected.
一切都按预期工作。
But if the response is like this
但如果回应是这样的
{"error":{"publicMsg":"Something bad happened", msg:"you forgot requesting some parameter"}}
{"error":{"publicMsg":"Something bad happened", msg:"you forgot requesting some parameter"}}
I'll get kind of an Dummy
object where foo
is null
and bar
is 0! The Gson documentation (fromJson) clearly states that:
我会得到一个Dummy
对象,其中foo
isnull
和bar
is 0!Gson 文档 (fromJson) 明确指出:
throws JsonSyntaxException - if json is not a valid representation for an object of type classOfT
抛出 JsonSyntaxException - 如果 json 不是 classOfT 类型的对象的有效表示
so i expected to get an JsonSyntaxException if I try to parse the second response like this:
所以如果我尝试像这样解析第二个响应,我希望得到一个 JsonSyntaxException:
Dummy dummy = Gson.fromJson(secondResponse, Dummy.class);
because the Json doesn't represent a Dummy object, but an ErrorHolder Object.
因为 Json 不代表 Dummy 对象,而是一个 ErrorHolder 对象。
So my question is: Is there a way, that Gson detects a wrong type somehow, and throws me an Exception?
所以我的问题是:有没有办法让 Gson 以某种方式检测到错误的类型,并向我抛出异常?
回答by Brian Roach
Unfortunately the documentation is slightly misleading there.
不幸的是,那里的文档有点误导。
It will only throw the exception if your class had a field whose type didn't match what is in the JSON, and even then it does some crazy things to try and fix it (converting an int
in the JSON to a String
in your class for example). If you had something like a Date
field in your POJO and it encountered an int
in the JSON, it'd throw it. Fields that are present in the JSON but not in your POJO are silently ignored, fields that are missing in the JSON but exist in your POJO are set to null
.
它只会抛出异常,如果你的类有一个字段的类型不匹配的是在JSON,而且当时做了一些疯狂的事情,试图修复它(将一个int
在JSON到String
你们班例如)。如果您Date
的 POJO 中有类似字段的内容,并且它遇到int
了 JSON 中的an ,它会抛出它。JSON 中存在但 POJO 中不存在的字段将被静默忽略,JSON 中缺失但 POJO 中存在的字段将设置为null
。
At present, GSON does not provide a mechanism for any sort of "strict" deserialization where you would have something like a @Required
annotation for fields in your POJO.
目前,GSON 没有为任何类型的“严格”反序列化提供机制,在这种情况下,您可以@Required
在 POJO 中对字段进行注释。
In your case ... I'd simply expand my POJO to include an inner error object ... something like:
在你的情况下......我只是扩展我的 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.
}
Your other option is to write a custom deserializer that throws the exception if the JSON is the error such as:
您的另一个选择是编写一个自定义反序列化器,如果 JSON 是错误,则抛出异常,例如:
class MyDeserializer implements JsonDeserializer<Dummy>
{
@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);
}
}
Edit to Add:Someone upvoted this recently and re-reading it I thought "Huh, you know, you could do this yourself and it might be handy".
编辑添加:最近有人对此表示赞同并重新阅读它,我想“嗯,你知道,你可以自己做这件事,它可能很方便”。
Here's a re-usable deserializer and annotation that will do exactly what the OP wanted. The limitation is that if the POJO required a custom deserializer as-is, you'd have to go a little further and either pass in a Gson
object in the constructor to deserialize to object itself or move the annotation checking out into a separate method and use it in your deserializer. You could also improve on the exception handling by creating your own exception and pass it to the JsonParseException
so it can be detected via getCause()
in the caller.
这是一个可重用的解串器和注释,可以完全满足 OP 的要求。限制是,如果 POJO 按原样需要自定义反序列化器,则您必须更进一步,要么Gson
在构造函数中传入一个对象以反序列化为对象本身,要么将检出的注解移到单独的方法中并使用它在你的解串器中。您还可以通过创建自己的异常并将其传递给 来改进异常处理,JsonParseException
以便getCause()
在调用者中检测到它。
That all said, in the vast majority of cases, this will work:
尽管如此,在绝大多数情况下,这将起作用:
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;
}
}
Output:
输出:
This is foo this is bar This is foo null Exception in thread "main" com.google.gson.JsonParseException: Missing field in JSON: foo
回答by Gabriel Stoenescu
I created an updated version of Brian's solution that handles nested objects and has a couple of other minor changes. The code also includes a simpler builder to create Gsonobjects that are aware of classes with fields annotated with JsonRequired.
我创建了一个更新版本的 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<T> implements JsonDeserializer<T> {
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<Field> findMissingFields(Object target, List<Field> 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<Field> 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<Field, String> toMessage = new Function<Field, String>() {
@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 <T> RequiredFieldAwareGsonBuilder withRequiredFieldAwareType(Class<T> classOfT) {
gsonBuilder.registerTypeAdapter(classOfT, new AnnotatedDeserializer<T>());
return this;
}
public Gson build() {
return gsonBuilder.create();
}
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public static @interface JsonRequired {
}
}
And the Reflection utility
和反射实用程序
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;
}
}
If you use Guice you could add something like this to your module to inject Gson objects
如果你使用 Guice 你可以在你的模块中添加这样的东西来注入 Gson 对象
@Provides
@Singleton
static Gson provideGson() {
return RequiredFieldAwareGsonBuilder.builder().withRequiredFieldAwareType(MyType1.class)
.withRequiredFieldAwareType(MyType2.class).build();
}
回答by dimo414
I'm not a fan of the selected solution. It works, but it's not the way to use Gson. Gson maps a particular JSON schema to an object and vice-versa. Ideally, the JSON you're using is well formed (so if you have control over the JSON format, consider changing it), but if not, you should design the parsing object to handle all cases you expect to receive.
我不喜欢所选的解决方案。它有效,但这不是使用 Gson 的方式。Gson 将特定的 JSON 模式映射到对象,反之亦然。理想情况下,您使用的 JSON 格式良好(因此如果您可以控制 JSON 格式,请考虑更改它),但如果不是,您应该设计解析对象来处理您希望接收的所有情况。
Sometimes you do need to write a custom JsonDeserializer
, but this is not one of those times. Sending a message orerror is a very standard practice, and with the right data structure GSON can handle such a simple use case directly.
有时您确实需要编写 custom JsonDeserializer
,但这不是其中之一。发送消息或错误是一种非常标准的做法,使用正确的数据结构 GSON 可以直接处理这样一个简单的用例。
If you have control over the JSON schema
如果您可以控制 JSON 架构
Consider something like this instead:
考虑这样的事情:
{
"message": {
"foo": "Hello World",
"bar": 3
},
"error": null;
}
{
"message": null,
"error": {
"publicMsg": "Something bad happened",
"msg": "you forgot requesting some parameter"
}
}
Notice you can now define a clean wrapper class that providesDummy
objects when possible:
请注意,您现在可以定义一个干净的包装类,在可能的情况下提供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;
}
}
If you have to deal with the existing JSON schema
如果您必须处理现有的 JSON 模式
If you can't restructure the schema, you have to restructure the parsing class, it would look something like this:
如果你不能重构架构,你必须重构解析类,它看起来像这样:
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;
}
}
This is less desirable than fixing the schema, but you get the same general API either way - call hasError()
to see if the request succeeded, then either call getDummy()
or getError()
as needed. Calls to the other method (e.g. getDummy()
when you recieved an error) will fail-fast.
这比修复架构更不理想,但您可以通过任何一种方式获得相同的通用 API - 调用hasError()
以查看请求是否成功,然后调用getDummy()
或getError()
根据需要。调用其他方法(例如,getDummy()
当您收到错误时)将快速失败。