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

2022-09-02 01:04:11

我在我的项目中使用Gson将JSON字符串反序列化为Java对象。如果我执行请求,我希望服务器提供明确定义的响应。服务器将返回我期望的明确定义的响应,或者它将返回我一个(也定义的)错误对象。

为了清楚起见:假设我有一个简单的对象,如下所示:

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

我将得到一个对象,其中是和等于0!Gson文档(来自Json)明确指出:Dummyfoonullbar

throws JsonSyntaxException - 如果 json 不是 classOfT 类型对象的有效表示形式

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

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

因为 Json 不表示虚拟对象,而是 ErrorHolder 对象。

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


答案 1

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

仅当您的类具有类型与JSON中的内容不匹配的字段时,它才会引发异常,即使这样,它也会做一些疯狂的事情来尝试修复它(例如,将JSON中的a转换为类中的a)。如果你的POJO中有一个类似字段的东西,并且在JSON中遇到一个,它会抛出它。JSON 中存在但 POJO 中不存在的字段将被静默忽略,JSON 中缺少但 POJO 中存在的字段将设置为 。intStringDateintnull

目前,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 是错误,则会引发异常,例如:

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

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

这是一个可重用的反序列化程序和注释,它将完全按照OP的需求执行操作。限制在于,如果 POJO 需要按原样自定义反序列化程序,则必须更进一步,要么在构造函数中传入对象以反序列化对象本身,要么将注释检出移动到单独的方法中并在反序列化程序中使用它。您还可以通过创建自己的异常并将其传递给 来改进异常处理,以便可以通过调用方检测到它。GsonJsonParseExceptiongetCause()

总而言之,在绝大多数情况下,这将起作用:

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

答案 2

我创建了 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();
}