【问题标题】:GSON ignore elements with wrong typeGSON 忽略类型错误的元素
【发布时间】:2015-01-28 12:37:38
【问题描述】:

我正在使用 Retrofit(结合 Ok​​Http 和 GSON)与在线 Web 服务进行通信。 网络服务有一个围绕它的所有响应的默认包装器,类似于:

{  
  "resultCode":"OK",
  "resultObj":"Can be a string or JSON object / array",
  "error":"",
  "message":""
}

在此示例中,resultCode 将是 OKNO。此外,errormessage 仅在处理请求时发生错误时才具有任何内容。最后但并非最不重要的一点是,resultObj 将包含调用的实际结果(在示例中为字符串,但有些调用返回 JSON 数组或 JSON 对象)。

为了处理这些元数据,我创建了一个通用类,如下所示:

public class ApiResult<T> {

    private String error;
    private String message;
    private String resultCode;
    private T resultObj;

    // + some getters, setters etcetera
}

我还创建了代表 resultObj 中有时给出的响应的类,并且我定义了一个用于 Retrofit 的接口,看起来有点像这样:

public interface SomeWebService {

    @GET("/method/string")
    ApiResult<String> someCallThatReturnsAString();

    @GET("/method/pojo")
    ApiResult<SomeMappedResult> someCallThatReturnsAnObject();

}

只要请求有效,这一切都可以正常工作。但是当服务器端发生错误时,它仍然会返回一个字符串类型的resultObj。这会导致 someCallThatReturnsAnObject 在 Retrofit RestAdapter / GSON 库中崩溃,并显示如下消息:

改造。改造错误: com.google.gson.JsonSyntaxException: java.lang.IllegalStateException:

预期为 BEGIN_OBJECT,但在第 1 行第 110 列路径 $.resultObj 处为 STRING

现在,最后,我的问题是:

  1. 是否有一种(简单的)方法可以告诉 GSON,如果它与预期类型不匹配,它应该忽略(也称为“无效”)一个属性?
  2. 我可以告诉 GSON 将空字符串视为 null 吗?

【问题讨论】:

    标签: java android json gson retrofit


    【解决方案1】:

    像这样定义你的模型:

    public class ApiResult {
    
        private String error;
        private String message;
        private String resultCode;
        private MyResultObject resultObj;
    }
    

    然后,为 MyResultObject 创建一个 TypeAdapterFactory

    public class MyResultObjectAdapterFactory implements TypeAdapterFactory {
    
        @Override
        @SuppressWarnings("unchecked")
        public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
            if (type.getRawType()!= MyResultObject.class) return null;
    
            TypeAdapter<MyResultObject> defaultAdapter = (TypeAdapter<MyResultObject>) gson.getDelegateAdapter(this, type);
            return (TypeAdapter<T>) new MyResultObjectAdapter(defaultAdapter);
        }
    
        public class MyResultObjectAdapter extends TypeAdapter<MyResultObject> {
    
            protected TypeAdapter<MyResultObject> defaultAdapter;
    
    
            public MyResultObjectAdapter(TypeAdapter<MyResultObject> defaultAdapter) {
                this.defaultAdapter = defaultAdapter;
            }
    
            @Override
            public void write(JsonWriter out, MyResultObject value) throws IOException {
                defaultAdapter.write(out, value);
            }
    
            @Override
            public MyResultObject read(JsonReader in) throws IOException {
                /* 
                This is the critical part. So if the value is a string,
                Skip it (no exception) and return null.
                */
                if (in.peek() == JsonToken.STRING) {
                    in.skipValue();
                    return null;
                }
                return defaultAdapter.read(in);
            }
        }
    }
    

    最后,为Gson注册MyResultObjectAdapterFactory

    Gson gson = new GsonBuilder()
        .registerTypeAdapterFactory(new MyResultObjectAdapterFactory())
        .create();
    

    现在,当使用该 Gson 对象反序列化 ApiResult json 时,如果 resultObj 将被设置为 null是一个字符串

    我希望这能解决你的问题 =)

    【讨论】:

    • 那么如何从 resultObj 中取出字符串?
    • 它可以工作,但是如果我们有几个需要这种修复的 bean 怎么办?我们是否应该一次又一次地实现这个适配器,这将是很多重复的代码?
    【解决方案2】:

    我也遇到过类似的问题,最后想出了以下解决方案:

    与其尝试将您的元素解析为 StringArray,不如尝试将数据存储到一个简单的 java.lang.Object 中

    这可以防止解析崩溃或抛出异常。

    例如。使用 GSON 注释,您的模型的属性将如下所示:

    @SerializedName("resultObj")
    @Expose
    private java.lang.Object resultObj;
    

    接下来,在运行时访问您的数据时,您可以检查您的resultObj 属性是否是String 的实例。

    if(apiResultObject instanceof String ){
        //Cast to string and do stuff
    
    } else{
        //Cast to array and do stuff
    
    }
    

    原帖:https://stackoverflow.com/a/34178082/3708094

    【讨论】:

    • 谢谢。帮助我。实现它的最简单方法。
    【解决方案3】:

    首先,这是您正在处理的糟糕的 API 设计。 :-(

    您可以使用自定义JsonDeserializer 来处理这种情况。

    用 Retrofit 注册它:

    MyJsonDeserializer deserializer = new MyJsonDeserializer()).create();
    final Gson gson = new GsonBuilder().registerTypeAdapter(ApiResult.class, deserializer);
    RestAdapter restAdapter = new RestAdapter.Builder()
        .setEndpoint(API_URL)
        .setConverter(new GsonConverter(gson))
        .build();
    

    【讨论】:

    • 您确定这将适用于泛型类吗?过去,我在使用 GSON 和通用类的自定义反序列化器时遇到了麻烦。 PS:我知道这是一个糟糕的设计,但不幸的是,我对 web 服务会发生什么没有发言权。
    • 实际上不确定泛型。只有一种方法可以找出答案。 :-)
    【解决方案4】:

    我正在重复使用我的响应 pojo。

    在一个响应中是String,另一个响应是List&lt;MajicalModel&gt; magicalField,所以一次解析失败。

    我将其更改为 com.google.gson.JsonElement magicalField; 它对我有用。这样它解析原始 json 并忽略类型不匹配。

    【讨论】:

      【解决方案5】:

      您也可以使用此代码:

      ObjectMapper objectMapper = new ObjectMapper();
      objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
      yourObject = objectMapper.readValue(jsonString, <ClassName>.class);
      

      【讨论】:

      • 您确定它与 GSON 相关,而不是 Jackson?
      猜你喜欢
      • 1970-01-01
      • 2019-08-28
      • 2012-11-22
      • 1970-01-01
      • 2022-11-30
      • 1970-01-01
      • 2021-11-29
      • 2011-06-29
      相关资源
      最近更新 更多