22

我在我的项目中使用 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"}}

我会得到一个Dummy对象在哪里foonullbar是0!Gson 文档(fromJson)明确指出:

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

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

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

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

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

4

3 回答 3

29

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

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

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

在你的情况下......我只是扩展我的 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 需要按原样自定义反序列化器,则您必须更进一步,要么在构造函数中传入一个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;

    }
}

输出:

这是富
这是酒吧
这是富
空值
线程“主”com.google.gson.JsonParseException 中的异常:JSON 中缺少字段:foo
于 2013-01-09T20:25:05.030 回答
4

我创建了 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();
}
于 2015-03-13T04:02:04.323 回答
2

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

有时您确实需要编写 custom 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(),当您收到错误时)将快速失败。

于 2015-11-23T23:57:55.280 回答