关于retrofit返回同字段不同类型数据的问题

Posted by 毛毛虫 on June 7, 2018

背景

使用Retrofit加Rxjava网络请求,配合FastJson进行解析,遇见问题。

案场还原

登录成功,接口返回如下

{
    "Status": 200,
    "Ticket": "84954b2d5691438591401f0dd415f21d",
    "ErrorMessage": "",
    "User": {
        "Id": 53,
        "Mobile": "13002179439",
        "Name": "李乐",
        "Nick": "李乐",
        "TIcon": "https://admin-test.hcjapp.com:50000/webImgServer/upload/office/201806/04/1c1acb47-1126-469d-939b-cfbdad5ec3ac.jpg",
        "CompanyId": "D93A2949320A4246AA2B9BFEB3900B84",
        "CompanyName": "总部",
        "RoomNum": "201",
        "CardNum": "E50BB8DD",
        "EmployeeNum": "201002",
        "IsGym": true
    },
    "Authority": {}
}

登录失败,接口返回如下

{
    "Status": 410,
    "Ticket": "-1",
    "ErrorMessage": "密码错误!",
    "User": "{}",
    "Authority": {}
}

可以看见User字段返回类型由一个对象变成了一个String字符串。 如果执行网络请求时还是使用的User对象泛型,当登陆失败返回User类型变为字符串之后就会报解析异常。

解决方式

解决方式一言以蔽之就是自定义json解析工厂类,在其中进行二次解析,当失败时捕获异常 这里写图片描述

  • 首先定义一个泛型类,在同字段不同类型处使用
public class LoginUserEntity<T> extends BaseModel {

    private int Status;
    private String Ticket;
    private String ErrorMessage;
    private T User;              //登录成功则泛型为User
    private AuthorityBean Authority;
}
  • 定义登录成功时返回的User字段类型
public class User{

    private int Id;
    private String Mobile;
    private String Name;
    private String Nick;
    private String TIcon;
    private String CompanyId;
    private String CompanyName;
    private String RoomNum;
    private String CardNum;
    private String EmployeeNum;
    private boolean IsGym;
}
  • 定义登录失败时返回的数据类型,这里的User字段就可以直接String
public class LoginUserErrorEntity extends BaseModel {

    private int Status;
    private String Ticket;
    private String ErrorMessage;
    private String User;       //登录失败,String类型
    private AuthorityBean Authority;
}
  • 自定义json解析工厂类继承Converter.Factory
public final class GsonDConverterFactory extends Converter.Factory {

    public static GsonDConverterFactory create() {
        return create(new Gson());
    }

    public static GsonDConverterFactory create(Gson gson) {
        return new GsonDConverterFactory(gson);
    }

    private final Gson gson;

    private GsonDConverterFactory(Gson gson) {
        if (gson == null) throw new NullPointerException("gson == null");
        this.gson = gson;
    }

    @Override public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
        return new GsonResponseBodyConverter < >(gson, type);
    }

    @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);
    }
}
  • 实现 Converter<T, RequestBody> 请求体接口
final class GsonRequestBodyConverter<T> implements Converter<T, RequestBody> {
    private static final MediaType MEDIA_TYPE = MediaType.parse("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());
    }
}
  • 重点来了!自定义类实现响应体,重写convert方法,进行二次解析。当登录成功时,返回code等于200,于是我们使用LoginUserEntity<User> 类型进行解析,当登录失败返回410时,我们使用LoginUserErrorEntity 进行解析(当然如果这里使用LoginUserEntity应该也是正确的),并抛出我们自定义的异常,一共后面的数据提取。
final class GsonResponseBodyConverter < T > implements Converter< ResponseBody,
        T > {
    private final Gson gson;
    private final Type type;

    GsonResponseBodyConverter(Gson gson, Type type) {
        this.gson = gson;
        this.type = type;
    }

    /**
     * 针对数据返回成功、错误不同类型字段处理
     */
    @Override public T convert(ResponseBody value) throws IOException {
        String response = value.string();
        try {
            // 这里的type实际类型是 LoginUserEntity<User>  User就是user字段的对象。
            LoginUserEntity result = gson.fromJson(response, LoginUserEntity.class);
            int code = result.getStatus();
            if (code == 200) {
                return gson.fromJson(response, type);
            } else {
                Log.d("HttpManager", "err==:" + response);
                LoginUserErrorEntity errResponse = gson.fromJson(response, LoginUserErrorEntity.class);
                if (code == 410) {
                    throw new ResultException(errResponse.getErrorMessage(), code);
                } else {
                    throw new ResultException(errResponse.getErrorMessage(), code);
                }
            }
        } finally {
            value.close();
        }
    }
}
  • 附上自定义异常代码

public class ResultException extends IOException {

    private String errMsg;
    private int errCode;

    public ResultException(String errMsg, int errCode){
        this.errMsg = errMsg;
        this.errCode = errCode;
    }

    public String getErrMsg() {
        return errMsg;
    }

    public void setErrMsg(String errMsg) {
        this.errMsg = errMsg;
    }

    public int getErrCode() {
        return errCode;
    }

    public void setErrCode(int errCode) {
        this.errCode = errCode;
    }
}

  • 使用我们自定义的json数据解析器
sRetrofit1 = new Retrofit.Builder()
                    .client(httpClient)
                    .baseUrl(API_LOGIN)
                    .addConverterFactory(GsonDConverterFactory.create())
                    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                    .build();

并且一定要保证我们的接口返回的是正确的完整的数据类型,如 LoginUserEntity<User>,否则会报错。

 //登录
        @POST("api/login/")
        @Headers("Content-Type: application/json")
        Observable<LoginUserEntity<User>> getAppLogin(@Body RequestBody body);

之后我们就使用retrofit加rxjava进行正常的网络流式请求。 要获取登陆失败时的数据,可以在Subscriber的onError中捕获异常,拿到数据,比如

@Override
	public void onError(Throwable e) {
		String error = "";
		try {
			if (e instanceof SocketTimeoutException) {
				error = "网络连接超时,请稍后再试...";
			} else if (e instanceof ConnectException) {
				error = "网络连接超时,请稍后再试...";
			} else if (e instanceof UnknownHostException) {
				error = "网络连接超时,请稍后再试...";
			} else {
				if (e instanceof ResultException) {
					error = ((ResultException) e).getErrMsg();   //抛出异常,抓取数据,拿到失败的errormsg
				} else {
//					error = "网络连接超时,请稍后再试...";
				}
			}
			ToolAlert.showCustomShortToast(error);     //alert管理
			onMyError(error);    //自定义的error
			Log.e("error", mContext.getPackageName() + "=====Error=======>" + e.toString());
			e.printStackTrace();
		} catch (IOException e1) {
			e1.printStackTrace();
		} finally {
			
		}
	}