我在我的项目中使用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
对象,其中foo
为null
且bar
为0! Gson文档(来自Json)明确指出:
抛出 JsonSyntaxException - 如果json不是一个有效的表示形式 classOfT类型的对象
所以如果我尝试解析第二个响应,我希望得到一个JsonSyntaxException:
Dummy dummy = Gson.fromJson(secondResponse, Dummy.class);
因为Json不代表Dummy对象,而是ErrorHolder对象。
所以我的问题是:有没有办法,Gson以某种方式检测到错误的类型,并抛出异常?
答案 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();
}