让Gson在错误的类型上抛出异常

时间:2013-01-09 16:51:50

标签: java json gson type-safety

我在我的项目中使用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以某种方式检测到错误的类型,并抛出异常?

2 个答案:

答案 0 :(得分:25)

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

如果你的类的字段类型与JSON中的字段不匹配,它只会抛出异常,即便如此,它也会尝试修复它(在{例如,JSON到您班级中的int。如果你的POJO中有类似String字段的东西,并且它在JSON中遇到Date,它就会抛出它。静默忽略JSON中但不存在于POJO中的字段,JSON中缺少但POJO中存在的字段设置为int

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

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

@Required

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

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.
}

编辑添加:有人最近对此进行了评价并重新阅读,我想“嗯,你知道,你可以自己做这件事,这可能很方便”。

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

大家都说,在绝大多数情况下,这都会有效:

getCause()

输出:

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

答案 1 :(得分:3)

我创建了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 {
}
}

反射实用程序

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();
}