Retrofit + Gson解析數(shù)據(jù)容錯(cuò)處理

使用Retrofit+Gson 處理網(wǎng)絡(luò)數(shù)據(jù)测蹲,后臺(tái)數(shù)據(jù)返回不規(guī)范,因?yàn)榉N種原因后臺(tái)無(wú)法修改篮赢,直接解析會(huì)報(bào)錯(cuò)JsonSyntaxException琉挖,有時(shí)需要我們做容錯(cuò)處理
(當(dāng)然可以解析時(shí)傳String,這時(shí)就需要我們手動(dòng)解析每個(gè)數(shù)據(jù)結(jié)構(gòu),比較麻煩)

數(shù)據(jù)不規(guī)范的奇葩問(wèn)題

問(wèn)題1:

int類型為空時(shí)寥茫,返回了“”矾麻,這時(shí)會(huì)報(bào)錯(cuò) java.lang.NumberFormatException: empty String

問(wèn)題2:

原始字段原來(lái)為String,后期修改為其他數(shù)據(jù)類型,但是未通知客戶端
(在解析數(shù)據(jù)的時(shí)候射富,無(wú)用字段最好去除)

問(wèn)題3:

有數(shù)據(jù)時(shí)返回對(duì)象,無(wú)數(shù)據(jù)時(shí)返回字符串 “”

有數(shù)據(jù)
{
"code": "0",
"message": "",
"result": {
"age":10
},

}
無(wú)數(shù)據(jù)
{
"code": "0",
"message": "",
"result": ""

}

問(wèn)題4 同一個(gè)字段名字限次,code 不同柴灯,返回不同的類型

這個(gè)沒(méi)有什么好辦法赠群,只能用object接收,根據(jù)code 手動(dòng)解析

為了解決這幾個(gè)問(wèn)題查描,我們需要了解retrofit 和gson的解析原理冬三,找到正確位置來(lái)添加我們的容錯(cuò)代碼

當(dāng)前分析的Gson版本號(hào)為2.8.5

 Gson gson = new Gson();
 gson.fromJson(str,BaseTest.class);

首先,創(chuàng)建gson初始化時(shí)敌蚜,會(huì)添加解析TypeAdapterFactory窝爪,后面主要使用它來(lái)解析數(shù)據(jù)


企業(yè)微信截圖_97bec518-e658-4398-87ab-645ae2c79165.png

下面步入正題,看Gson.formJson()內(nèi)部纷跛,最終調(diào)用
public <T> T fromJson(JsonReader reader, Type typeOfT)

public <T> T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, JsonSyntaxException {
   ...
    try {
      reader.peek();
      isEmpty = false;
      TypeToken<T> typeToken = (TypeToken<T>) TypeToken.get(typeOfT);
      TypeAdapter<T> typeAdapter = getAdapter(typeToken);
      T object = typeAdapter.read(reader);
      return object;
    } catch (EOFException e) {
     ...
  }

根據(jù)typeToken獲取解析的adapter (gson初始化是默認(rèn)添加的TypeAdapterFactory獲取)

  public <T> TypeAdapter<T> getAdapter(TypeToken<T> type) {
    TypeAdapter<?> cached = typeTokenCache.get(type == null ? NULL_KEY_SURROGATE : type);
  ...

    try {
     ...
      for (TypeAdapterFactory factory : factories) {
        TypeAdapter<T> candidate = factory.create(this, type);
        if (candidate != null) {
          call.setDelegate(candidate);
          typeTokenCache.put(type, candidate);
          return candidate;
        }
      }
      throw new IllegalArgumentException("GSON (" + GsonBuildConfig.VERSION + ") cannot handle " + type);
    } finally {
     ...
    }
  }

常用factory生成類:TypeAdapters 各種基礎(chǔ)解析器(int String...)
其中自定義類factory 為ReflectiveTypeAdapterFactory
會(huì)解析整個(gè)類忽舟,拿到具體數(shù)據(jù)類型叮阅,進(jìn)而調(diào)用上一步的方法gson. getAdapter()解析基礎(chǔ)類型

gson提供了自定義解析器的方法
方法1: 直接定義factoty

  Gson gson = new Gson().newBuilder()
.registerTypeAdapterFactory(new MyTypeAdapterFactory<Test>()).create();

   public class MyTypeAdapterFactory<T> implements TypeAdapterFactory {
        @SuppressWarnings("unchecked")
        public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
            Class<T> rawType = (Class<T>) type.getRawType();
            if (rawType == String.class) {
                return (TypeAdapter<T>) new MyAdapter();
            }if(rawType == int.class){
                return (TypeAdapter<T>)IntTypeAdapter;
            }
            return null;
        }
    }

方法2: 定義adapter

 gson = new GsonBuilder()
                .setLenient()
                .registerTypeAdapter(long.class, LongTypeAdapter)
                .registerTypeAdapter(Long.class, LongTypeAdapter)
                .registerTypeAdapter(int.class, IntTypeAdapter)
                .registerTypeAdapter(Integer.class, IntTypeAdapter)
                .registerTypeAdapter(String.class,StringTypeAdapter)
         .create();

到此浩姥,我們可以解決 問(wèn)題1 問(wèn)題2

  1. int 字端,但后臺(tái)返回“”兜挨,這時(shí)會(huì)報(bào)錯(cuò) java.lang.NumberFormatException: empty String
    private  TypeAdapter<Number> IntTypeAdapter = new TypeAdapter<Number>() {
        @Override
        public void write(JsonWriter out, Number value) throws IOException {
            out.value(value);
        }

        @Override
        public Number read(JsonReader in) throws IOException {
            if (in.peek() == JsonToken.NULL) {
                in.nextNull();
                return null;
            }
            try {
                String result = in.nextString();
                if ("".equals(result)) {
                    return null;
                }
                return Integer.parseInt(result);
            } catch (NumberFormatException e) {
                throw new JsonSyntaxException(e);
            }
        }
    };
  1. String 字端眯分,但后臺(tái)返回了對(duì)象
    private TypeAdapter<String> StringTypeAdapter = new TypeAdapter<String>() {
        @Override
        public void write(JsonWriter out, String value) throws IOException {
            LogUtils.e("write");
            out.value(value);
        }

        @Override
        public String read(JsonReader in) throws IOException {
           // LogUtils.e("read");
            if (in.peek() == JsonToken.NULL) {
                in.nextNull();
                return null;
            }

            // number類的也會(huì)執(zhí)行這個(gè)方法弊决,但是number返回null 會(huì)報(bào)錯(cuò),所以把Number排除
            if(in.peek() != JsonToken.STRING && in.peek() != JsonToken.NUMBER){
               // LogUtils.e("typeAdapter","stringTypeAdapter 不是String = "+in.peek());
           //不是String与倡,直接跳過(guò)昆稿,不解析
                in.skipValue();
                return null;
            }
            String result = in.nextString();

           // LogUtils.e("typeAdapter","stringTypeAdapter ="+result);
            return result;
    }};

問(wèn)題3 通過(guò)自定義一個(gè) ReflectiveTypeAdapterFactory可能也可以實(shí)現(xiàn)溉潭,但是比較麻煩

代碼中使用retrofit+gson,查看報(bào)錯(cuò)位置

解析過(guò)程中okhttpCall ---> callback.onFailure(OkHttpCall.this, e);
java.lang.IllegalStateException: Expected STRING but was BEGIN_OBJECT at line 1 column 353 path $.data....

下面看一下retrofit+gson實(shí)現(xiàn)解析的過(guò)程
當(dāng)前分析的retrofit版本號(hào)為2.6.0

首先retrofit 入口處

 Retrofit.Builder builder = new Retrofit.Builder();
      ...
//自定義解析器
  builder.addConverterFactory(GsonConverterFactory.create(gson));
  retrofitsingleton = builder.build();
  return retrofitsingleton.create(clazz);

Retrofit.create()方法動(dòng)態(tài)代理

  public <T> T create(final Class<T> service) {
   ...
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
          private final Platform platform = Platform.get();
          private final Object[] emptyArgs = new Object[0];

          @Override public @Nullable Object invoke(Object proxy, Method method,
              @Nullable Object[] args) throws Throwable {
               ...
            return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
          }
        });
  }

繼續(xù)跟進(jìn) loadServiceMethod(method).invoke()方法
ServiceMethod ---HttpServiceMethod.invoke()

@Override final @Nullable ReturnT invoke(Object[] args) {
    Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
    return adapt(call, args);
  }

Call adapt(call args) 最終返回的是okhttpCall
最終調(diào)用 okhttpCall.enqueue()岛抄,可以看到解析數(shù)據(jù)位置

  @Override public void enqueue(final Callback<T> callback) {
  
    call.enqueue(new okhttp3.Callback() {
      @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
        Response<T> response;
        try {
       //***********************
        //***解析數(shù)據(jù)位置
       //***********************
          response = parseResponse(rawResponse);
        } catch (Throwable e) {
          throwIfFatal(e);
        //***********************
        //***失敗調(diào)用callback.onFailure(OkHttpCall.this, e);
       //***********************
          callFailure(e);
          return;
        }

        try {
          callback.onResponse(OkHttpCall.this, response);
        } catch (Throwable t) {
          throwIfFatal(t);
          t.printStackTrace(); // TODO this is not great
        }
      }

      @Override public void onFailure(okhttp3.Call call, IOException e) {
        callFailure(e);
      }

      private void callFailure(Throwable e) {
        try {
          callback.onFailure(OkHttpCall.this, e);
        } catch (Throwable t) {
          throwIfFatal(t);
          t.printStackTrace(); // TODO this is not great
        }
      }
    });
  }

接下來(lái)看具體解析過(guò)程 response=parseResponse(rawResponse)

Converter<ResponseBody, T> responseConverter;
  Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
    ResponseBody rawBody = rawResponse.body();
 ...
  T body = responseConverter.convert(catchingBody);
      return Response.success(body, rawResponse);
···
  }

可以看到解析為 Converter<ResponseBody, T> responseConverter;
responseConverter 在 Retrofit build時(shí)初始化 (BuiltInConverters)
retrofit提供了自定義方法:
public Builder addConverterFactory(Converter.Factory factory) {
需要我們自定義ConverterFactory
可以參考 api 'com.squareup.retrofit2:converter-gson:2.6.0'中
GsonConverterFactory

下面通過(guò)自定義ConverterFactory夫椭,解決第三個(gè)問(wèn)題

3.有數(shù)據(jù)時(shí)返回的是對(duì)象氯庆,無(wú)數(shù)據(jù)的時(shí)候返回了“”
{
"code": "0",
"message": "",
"result": [{
"age":10
}],

}
{
"code": "0",
"message": "",
"result": ""

}

簡(jiǎn)單處理,當(dāng)獲取數(shù)據(jù)為“”修改為 result = null

package com.nucarf.base.retrofit.gson;

import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.reflect.TypeToken;

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;

import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Converter;
import retrofit2.Retrofit;

public final class GsonConverterFactory extends Converter.Factory {
    /**
     * Create an instance using a default {@link Gson} instance for conversion. Encoding to JSON and
     * decoding from JSON (when no charset is specified by a header) will use UTF-8.
     */
    public static GsonConverterFactory create() {
        return create(new Gson());
    }

    /**
     * Create an instance using {@code gson} for conversion. Encoding to JSON and
     * decoding from JSON (when no charset is specified by a header) will use UTF-8.
     */
    @SuppressWarnings("ConstantConditions") // Guarding public API nullability.
    public static GsonConverterFactory create(Gson gson) {
        if (gson == null) throw new NullPointerException("gson == null");
        return new GsonConverterFactory(gson);
    }

    private final Gson gson;

    private GsonConverterFactory(Gson gson) {
        this.gson = gson;
    }

    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
                                                            Retrofit retrofit) {
        TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
        return new GsonResponseBodyConverter<>(gson, adapter);
    }

    @Override
    public Converter<?, RequestBody> requestBodyConverter(Type type,
                                                          Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
        TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
        return new GsonRequestBodyConverter<>(gson, adapter);
    }
}



package com.nucarf.base.retrofit.gson;

import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonWriter;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.Charset;

import okhttp3.MediaType;
import okhttp3.RequestBody;
import okio.Buffer;
import retrofit2.Converter;

/**
 * author : li
 * date   : 2020/10/13/1:43 PM
 */
final class GsonRequestBodyConverter<T> implements Converter<T, RequestBody> {
    private static final MediaType MEDIA_TYPE = MediaType.get("application/json; charset=UTF-8");
    private static final Charset UTF_8 = Charset.forName("UTF-8");

    private final Gson gson;
    private final TypeAdapter<T> adapter;

    GsonRequestBodyConverter(Gson gson, TypeAdapter<T> adapter) {
        this.gson = gson;
        this.adapter = adapter;
    }

    @Override public RequestBody convert(T value) throws IOException {
        Buffer buffer = new Buffer();
        Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8);
        JsonWriter jsonWriter = gson.newJsonWriter(writer);
        adapter.write(jsonWriter, value);
        jsonWriter.close();
        return RequestBody.create(MEDIA_TYPE, buffer.readByteString());
    }
}


package com.nucarf.base.utils;

import com.google.gson.Gson;
import com.google.gson.JsonIOException;
import com.google.gson.JsonSyntaxException;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

import okhttp3.ResponseBody;
import retrofit2.Converter;

package com.nucarf.base.utils;

import com.google.gson.Gson;
import com.google.gson.JsonIOException;
import com.google.gson.JsonSyntaxException;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

import okhttp3.ResponseBody;
import retrofit2.Converter;

/**
 * author : li
 * date   : 2020/10/13/1:42 PM
 */
final class GsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
    private final Gson gson;
    private final TypeAdapter<T> adapter;

    GsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {
        this.gson = gson;
        this.adapter = adapter;
    }

    @Override public T convert(ResponseBody value) throws IOException {
        JsonReader jsonReader = gson.newJsonReader(value.charStream());
        try {
            T result = adapter.read(jsonReader);
            if (jsonReader.peek() != JsonToken.END_DOCUMENT) {
                throw new JsonIOException("JSON document was not fully consumed.");
            }
            return result;
        } catch (JsonSyntaxException e) {//當(dāng)catch到這個(gè)錯(cuò)誤說(shuō)明gson解析錯(cuò)誤
            return null;
        } finally {
            value.close();
        }
    }

}


  Retrofit.Builder builder = new Retrofit.Builder();
      ...
  //調(diào)用
   builder.addConverterFactory(GsonConverterFactory.create(gson));
   return  builder.build().create(clazz);

問(wèn)題4 同一個(gè)字段名字洞豁,code 不同丈挟,返回不同的類型

只能用object接收,可以在baseresult中 解析

public class BaseResult<T> {
    private String code;
    private T result;
    private Object message;//字段名字曙咽,code 不同返回不同的類型

    public Object getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.msg = message;
    }

    //手動(dòng)解析
    public boolean isSuccessed() {
        try {
            if (code.equals(RetrofitConfig.STATUS_NCARF_SUCCESS)||code.equals(RetrofitConfig.STATUS_NCARF_DEVIC_ERROR)) {
                if (getMessage() instanceof LinkedTreeMap) {
                    Gson gson = new Gson();
                    String json = gson.toJson(getMessage());
                    MessageBean messageBean = gson.fromJson(json,MessageBean.class);
                    ···            
    }
                return true;
            }  else {
                if (getMessage() instanceof String && !TextUtils.isEmpty(getMessage().toString())) {
                    ToastUtils.show_middle_pic(R.mipmap.icon_toast_error, getMessage() + "", 0);
                }
                ···
                return false;
            }
        } catch (Exception e) {
            ToastUtils.show_middle_pic(R.mipmap.icon_toast_error, "網(wǎng)絡(luò)錯(cuò)誤", 0);

            return false;
        }
    }
}

參考
https://blog.csdn.net/u012422440/article/details/48860893
http://www.reibang.com/p/f723a5ac6e37
https://blog.csdn.net/u013064618/article/details/53486604?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末孝情,一起剝皮案震驚了整個(gè)濱河市洒嗤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌羔挡,老刑警劉巖派撕,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異镀赌,居然都是意外死亡际跪,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門良姆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)幔戏,“玉大人,你說(shuō)我怎么就攤上這事痊剖。” “怎么了陆馁?”我有些...
    開(kāi)封第一講書人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵叮贩,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我彪蓬,道長(zhǎng)杨箭,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任捣郊,我火速辦了婚禮慈参,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘驮配。我一直安慰自己壮锻,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布灰殴。 她就那樣靜靜地躺著掰邢,像睡著了一般。 火紅的嫁衣襯著肌膚如雪辣之。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,760評(píng)論 1 289
  • 那天狮鸭,我揣著相機(jī)與錄音怕篷,去河邊找鬼酗昼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛麻削,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播叠荠,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼扫责,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了者娱?” 一聲冷哼從身側(cè)響起苏揣,我...
    開(kāi)封第一講書人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤平匈,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后增炭,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡梅垄,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年哎甲,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了饲嗽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡吞加,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出叶圃,到底是詐尸還是另有隱情践图,我是刑警寧澤,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布德崭,位于F島的核電站揖盘,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏兽狭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一服球、第九天 我趴在偏房一處隱蔽的房頂上張望销钝。 院中可真熱鬧,春花似錦座享、人聲如沸似忧。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)饺著。三九已至,卻和暖如春幼衰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背梢睛。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留深碱,地道東北人藏畅。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像墓赴,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348