Retrofit + Gson解析数据容错处理

使用Retrofit+Gson 处理网络数据,后台数据返回不规范,因为种种原因后台无法修改,直接解析会报错JsonSyntaxException,有时需要我们做容错处理
(当然可以解析时传String,这时就需要我们手动解析每个数据结构,比较麻烦)

数据不规范的奇葩问题

问题1:

int类型为空时,返回了“”,这时会报错 java.lang.NumberFormatException: empty String

问题2:

原始字段原来为String,后期修改为其他数据类型,但是未通知客户端
(在解析数据的时候,无用字段最好去除)

问题3:

有数据时返回对象,无数据时返回字符串 “”

有数据
{
"code": "0",
"message": "",
"result": {
"age":10
},

}
无数据
{
"code": "0",
"message": "",
"result": ""

}

问题4 同一个字段名字,code 不同,返回不同的类型

这个没有什么好办法,只能用object接收,根据code 手动解析

为了解决这几个问题,我们需要了解retrofit 和gson的解析原理,找到正确位置来添加我们的容错代码

当前分析的Gson版本号为2.8.5

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

首先,创建gson初始化时,会添加解析TypeAdapterFactory,后面主要使用它来解析数据


企业微信截图_97bec518-e658-4398-87ab-645ae2c79165.png

下面步入正题,看Gson.formJson()内部,最终调用
public T fromJson(JsonReader reader, Type typeOfT)

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

根据typeToken获取解析的adapter (gson初始化是默认添加的TypeAdapterFactory获取)

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

    try {
     ...
      for (TypeAdapterFactory factory : factories) {
        TypeAdapter 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 各种基础解析器(int String...)
其中自定义类factory 为ReflectiveTypeAdapterFactory
会解析整个类,拿到具体数据类型,进而调用上一步的方法gson. getAdapter()解析基础类型

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

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

   public class MyTypeAdapterFactory implements TypeAdapterFactory {
        @SuppressWarnings("unchecked")
        public  TypeAdapter create(Gson gson, TypeToken type) {
            Class rawType = (Class) type.getRawType();
            if (rawType == String.class) {
                return (TypeAdapter) new MyAdapter();
            }if(rawType == int.class){
                return (TypeAdapter)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();

到此,我们可以解决 问题1 问题2

  1. int 字端,但后台返回“”,这时会报错 java.lang.NumberFormatException: empty String
    private  TypeAdapter IntTypeAdapter = new TypeAdapter() {
        @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 字端,但后台返回了对象
    private TypeAdapter StringTypeAdapter = new TypeAdapter() {
        @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类的也会执行这个方法,但是number返回null 会报错,所以把Number排除
            if(in.peek() != JsonToken.STRING && in.peek() != JsonToken.NUMBER){
               // LogUtils.e("typeAdapter","stringTypeAdapter 不是String = "+in.peek());
           //不是String,直接跳过,不解析
                in.skipValue();
                return null;
            }
            String result = in.nextString();

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

问题3 通过自定义一个 ReflectiveTypeAdapterFactory可能也可以实现,但是比较麻烦

代码中使用retrofit+gson,查看报错位置

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

下面看一下retrofit+gson实现解析的过程
当前分析的retrofit版本号为2.6.0

首先retrofit 入口处

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

Retrofit.create()方法动态代理

  public  T create(final Class 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);
          }
        });
  }

继续跟进 loadServiceMethod(method).invoke()方法
ServiceMethod ---HttpServiceMethod.invoke()

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

Call adapt(call args) 最终返回的是okhttpCall
最终调用 okhttpCall.enqueue(),可以看到解析数据位置

  @Override public void enqueue(final Callback callback) {
  
    call.enqueue(new okhttp3.Callback() {
      @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
        Response response;
        try {
       //***********************
        //***解析数据位置
       //***********************
          response = parseResponse(rawResponse);
        } catch (Throwable e) {
          throwIfFatal(e);
        //***********************
        //***失败调用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
        }
      }
    });
  }

接下来看具体解析过程 response=parseResponse(rawResponse)

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

可以看到解析为 Converter responseConverter;
responseConverter 在 Retrofit build时初始化 (BuiltInConverters)
retrofit提供了自定义方法:
public Builder addConverterFactory(Converter.Factory factory) {
需要我们自定义ConverterFactory
可以参考 api 'com.squareup.retrofit2:converter-gson:2.6.0'中
GsonConverterFactory

下面通过自定义ConverterFactory,解决第三个问题

3.有数据时返回的是对象,无数据的时候返回了“”
{
"code": "0",
"message": "",
"result": [{
"age":10
}],

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

}

简单处理,当获取数据为“”修改为 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 responseBodyConverter(Type type, Annotation[] annotations,
                                                            Retrofit retrofit) {
        TypeAdapter adapter = gson.getAdapter(TypeToken.get(type));
        return new GsonResponseBodyConverter<>(gson, adapter);
    }

    @Override
    public Converter 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 implements Converter {
    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 adapter;

    GsonRequestBodyConverter(Gson gson, TypeAdapter 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 implements Converter {
    private final Gson gson;
    private final TypeAdapter adapter;

    GsonResponseBodyConverter(Gson gson, TypeAdapter 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) {//当catch到这个错误说明gson解析错误
            return null;
        } finally {
            value.close();
        }
    }

}


  Retrofit.Builder builder = new Retrofit.Builder();
      ...
  //调用
   builder.addConverterFactory(GsonConverterFactory.create(gson));
   return  builder.build().create(clazz);

问题4 同一个字段名字,code 不同,返回不同的类型

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

public class BaseResult {
    private String code;
    private T result;
    private Object message;//字段名字,code 不同返回不同的类型

    public Object getMessage() {
        return message;
    }

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

    //手动解析
    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, "网络错误", 0);

            return false;
        }
    }
}

参考
https://blog.csdn.net/u012422440/article/details/48860893
https://www.jianshu.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

你可能感兴趣的:(Retrofit + Gson解析数据容错处理)