How do I parse Volley JSON Object with Gson? - java

I'm trying to parse this JSON Response:
{
"error": false,
"code": 200,
"message": "App Version",
"source": "Cache",
"data": {
"latestVersion": true,
"link": "-"
}
}
Here's the interface and volley instance code:
public interface IResult {
public void notifySuccess(String requestType, JSONObject response);
public void notifyError(String requestType, VolleyError error);
}
public class VolleyService {
IResult mResultCallback;
Context mCtx;
public VolleyService(IResult resultCallback, Context context) {
mResultCallback = resultCallback;
mCtx = context;
}
public void postData(final String requestType, String url, Map < String, String > data, Map < String, String > headers) {
try {
RequestQueue queue = Volley.newRequestQueue(mCtx);
JsonObjectRequest jsonObj = new JsonObjectRequest(url, new JSONObject(data), response - > {
if (mResultCallback != null)
mResultCallback.notifySuccess(requestType, response);
}, error - > {
if (mResultCallback != null) {
mResultCallback.notifyError(requestType, error);
if (error instanceof TimeoutError || error instanceof NoConnectionError) {
new DialogBuilder(mCtx, "Error: 1");
} else if (error instanceof AuthFailureError) {
new DialogBuilder(mCtx, "Error: 2");
} else if (error instanceof ServerError) {
new DialogBuilder(mCtx, "Error: 3");
} else if (error instanceof NetworkError) {
new DialogBuilder(mCtx, "Error: 4");
} else if (error instanceof ParseError) {
new DialogBuilder(mCtx, "Error: 5");
}
}
}) {
#Override
public Map < String, String > getHeaders() {
return headers;
}
};
queue.add(jsonObj);
} catch (Exception e) {
Log.e("postData", e.getMessage());
}
}
}
The JSONObject response is fetched from Volley and I try to parse the response with this code below:
public class CheckForUpdate {
private Context mCtx;
private IResult mResultCallback = null;
private Gson gson;
public CheckForUpdate(Context mCtx) {
this.mCtx = mCtx;
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
gson = gsonBuilder.setPrettyPrinting().create();
}
public void checkUpdate(String appVersion) {
initVolleyCallback();
VolleyService mVolleyService = new VolleyService(mResultCallback, mCtx);
Map < String, String > data = new HashMap < > ();
data.put("show", "version");
data.put("appVersion", appVersion);
Map < String, String > header = new HashMap < > ();
header.put("Content-Type", "application/json");
mVolleyService.postData("checkUpdate", URLs.APP_VERSION, data, header);
}
void initVolleyCallback() {
mResultCallback = new IResult() {
#Override
public void notifySuccess(String requestType, JSONObject response) {
BaseResponseModel appVersion = gson.fromJson(response.toString(), BaseResponseModel.class);
Log.e("appVersionJson", response.toString());
Log.e("appVersion", "message: " + appVersion.getMessage());
}
#Override
public void notifyError(String requestType, VolleyError error) {
}
};
}
}
I'm getting a null response in all those Logs.e, here's my models using Lombok look like:
BaseResponseModel.java
import java.io.Serializable;
import lombok.Data;
#Data
public class BaseResponseModel<T> implements Serializable {
private boolean error;
private float code;
private String message;
private String source;
private T data;
}
AppVersionModel.java
import java.io.Serializable;
import lombok.Data;
#Data
public class AppVersionModel implements Serializable {
private boolean latestVersion;
private String link;
}
How can I parse JSONObject response from Volley with Gson properly? What am I missing here?
Any help will be much appreciated.
Thanks.

You haven't posted the onCreate as well. It seems you haven't initialized GSon, GSon Builder.
Gson gson = new Gson();
The problem is here an empty constructor. Obviously it will retrieve null objects.
Try making use of this guide and work with it.

Related

I'd like to know how to parse json to retrieve the admob ID

I'd like to know how to parse a json object which will be uploaded to my server to retrieve the admob ad IDs from it.
Example:
{
"response":{
"Interstial AD":"ca-xxxxxxxxxxx"
}
}
will be sent to
mInterstitialAd = new InterstitialAd(this);
mInterstitialAd.setAdUnitId("JsonDATA");
mInterstitialAd.loadAd(new AdRequest.Builder().build());
mInterstitialAd.setAdListener(new AdListener(){
#Override
public void onAdLoaded() {
super.onAdLoaded();
mInterstitialAd.show();
}
}
Any help would be appreciated!
EDIT:
Tired this:
String jsonToProcess = "https://drive.google.com/uc?id=113RUepiYecy5pBwj-t4BtBXwlQwgf-dU";
String interstialAd = new JsonParser().parse(jsonToProcess).getAsJsonObject()
.get("response").getAsJsonObject()
.get("Interstial AD").getAsString();
if (getResources().getString(R.string.admob_interstitial_id).length() > 0
&& Config.INTERSTITIAL_INTERVAL > 0
&& !SettingsFragment.getIsPurchased(this)) {
mInterstitialAd = new InterstitialAd(this);
mInterstitialAd.setAdUnitId(interstialAd);
AdRequest adRequestInter = new AdRequest.Builder().addTestDevice(AdRequest.DEVICE_ID_EMULATOR).build();
mInterstitialAd.loadAd(adRequestInter);
mInterstitialAd.setAdListener(new AdListener() {
#Override
public void onAdClosed() {
// Load the next interstitial.
mInterstitialAd.loadAd(new AdRequest.Builder().addTestDevice(AdRequest.DEVICE_ID_EMULATOR).build());
}
});
}
My Json file:
{
"response": [
{
"Interstial AD": "ca-app-pub-3940256099942544/1033173712"
},
{
"Banner AD": "ca-app-pub-3940256099942544/6300978111"
}
]
}
Using Gson it's pretty easy to do this...
Option 1:
Edit per question edit:
String jsonToProcess = "your json string here"
JsonElement jsonElement = new JsonParser().parse(json);
String iterstialAd = null;
String bannerAd = null;
for (JsonElement obj : jsonElement.getAsJsonObject().get("response").getAsJsonArray()) {
if (obj.getAsJsonObject().get("Interstial AD") != null) {
iterstialAd = obj.getAsJsonObject().get("Interstial AD").getAsString();
continue;
}
if (obj.getAsJsonObject().get("Banner AD") != null) {
bannerAd = obj.getAsJsonObject().get("Banner AD").getAsString();
continue;
}
}
Option 2:
Create response model and deserialize your response...
import com.google.gson.annotations.SerializedName;
public class ResponseModel {
private Response response;
public Response getResponse() {
return response;
}
public void setResponse(Response response) {
this.response = response;
}
public static class Response {
#SerializedName("Interstial AD")
public String interstialAd;
public String getInterstialAd() {
return interstialAd;
}
public void setInterstialAd(String interstialAd) {
this.interstialAd = interstialAd;
}
}
}
And after this you can do something like:
String jsonToProcess = "your json string here";
ResponseModel model = new Gson().fromJson(jsonResponse, ResponseModel.class);
String interstialAd = model.getResponse().getInterstialAd();
Note that there are many libraries for json manipulation beside GSon, like Jackson, Json from org.json, ...
Hope this helps you.

How to change local/global or return variable from volley method

Is it possible to change the value of global variable or to return value from volley method. Im trying to return value but im not getting any value from this method. I need to return 'listaFilmovaSerija' ArrayList.
public static ArrayList<MoviesShowsModel> readJSON(Context context, String url, final ArrayList<MoviesShowsModel> listaFilmovaSerija, final boolean odredjenaVelicina){
requestQueue = Volley.newRequestQueue(context);
listaFilmovaSerija.clear();
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(Request.Method.GET,
url, null,
new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
try {
String naslov = "";
JSONObject obj = new JSONObject(response.toString());
JSONArray arr = obj.getJSONArray("results");
int d = (odredjenaVelicina)? 10 : arr.length();
for (int i = 0; i < d; i++){
JSONObject obj2 = arr.getJSONObject(i);
naslov = (getTabActive() == 0)? obj2.getString("title") : obj2.getString("name");
listaFilmovaSerija.add(new MoviesShowsModel(naslov,
obj2.getString("poster_path"),
obj2.getString("overview"),
obj2.getString("backdrop_path"),
obj2.getInt("id")));
}
} catch (JSONException e) {
e.printStackTrace();
}
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Log.i("ISPIS","Desila se greska " + error);
}
});
requestQueue.add(jsonObjectRequest);
return listaFilmovaSerija;}
To get the response in ArrayList you have to implement Custom request of type ArrayList then only you will be able to get the desired response.
You can read about it in official documentation
and you can find tutorial here
Example from Official documentation which is Custom GsonRequest
public class GsonRequest<T> extends Request<T> {
private final Gson gson = new Gson();
private final Class<T> clazz;
private final Map<String, String> headers;
private final Listener<T> listener;
/**
* Make a GET request and return a parsed object from JSON.
*
* #param url URL of the request to make
* #param clazz Relevant class object, for Gson's reflection
* #param headers Map of request headers
*/
public GsonRequest(String url, Class<T> clazz, Map<String, String> headers,
Listener<T> listener, ErrorListener errorListener) {
super(Method.GET, url, errorListener);
this.clazz = clazz;
this.headers = headers;
this.listener = listener;
}
#Override
public Map<String, String> getHeaders() throws AuthFailureError {
return headers != null ? headers : super.getHeaders();
}
#Override
protected void deliverResponse(T response) {
listener.onResponse(response);
}
#Override
protected Response<T> parseNetworkResponse(NetworkResponse response) {
try {
String json = new String(
response.data,
HttpHeaderParser.parseCharset(response.headers));
return Response.success(
gson.fromJson(json, clazz),
HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException e) {
return Response.error(new ParseError(e));
} catch (JsonSyntaxException e) {
return Response.error(new ParseError(e));
}
}
}

Response from Retrofit can't get object value

{ "StatusCode": 200, "StatusDescription": "OK", "ErrorMessage":
"", "ErrorDetail": "", "Results": [
{
"Key": "AccessTokens",
"Value": "[{\"Key\":\"XXXXX",
\"Value\":\"BABABA\"},{\"Key\":\"DIDADIDA\",\"Value\":\"YYYYY"
} ]"}]}
This is the response i will get when i success call the API. The datatype of "Results" is List. Can anyone explain for me how to get the "Key" and the "Value".
My Object Classes
public class KeyValueItem {
private String Key;
private String Value;
public String getKey() {
return Key;
}
public void setKey(String key) {
Key = key;
}
public String getValue() {
return Value;
}
public void setValue(String value) {
Value = value;
}
}
Response Class
public class RestServiceResponse {
#SerializedName("StatusCode")
#Expose
public int StatusCode;
public int getStatusCode() {
return StatusCode;
}
#SerializedName("StatusDescription")
#Expose
public String StatusDescription;
public String getStatusDescription() {
return StatusDescription;
}
#SerializedName("ErrorMessage")
#Expose
public String ErrorMessage;
public String getErrorMessage() {
return ErrorMessage;
}
#SerializedName("ErrorDetail")
#Expose
public String ErrorDetail;
public String getErrorDetail() {
return ErrorDetail;
}
#SerializedName("Results")
#Expose
public List<KeyValueItem> Results;
public List<KeyValueItem> getResults() {
return Results;
}
}
Anyone help please =(
Some of my code:
public void onResponse(Call<RestServiceResponse> call, Response<RestServiceResponse> response) {
Log.i("ddsddsadsa", String.valueOf(response.code()));
RestServiceResponse restServiceResponse = response.body();
if(restServiceResponse.getStatusCode() == 200){
List<KeyValueItem> list = response.body().getResults();
JSONArray jsonArray = new JSONArray(list);
try {
JSONObject job = jsonArray.getJSONObject(1);
String testttt = job.getString("Key");
Log.i("dsadsadsadas", testttt);
} catch (JSONException e) {
e.printStackTrace();
}
}
2 things you have to understand first.
Your JSON data is not in valid format. It contains \ (slashes) to escape double quotes in key-value pair. To confirm whether the returned JSON data is valid or not please copy & paste your JSON response into JSON validator and Formatter. Maybe problem in server script.
If you're using GsonConvertorFactory with Retrofit, Retrofit will automatically converts JSON response data to POJO internally. So, you don't need parse it again inside onResponse() method. If you get proper JSON response from server side then use it like below.
public void onResponse(Call<RestServiceResponse> call, Response<RestServiceResponse> response) {
// code....
RestServiceResponse restServiceResponse = response.body();
if (restServiceResponse.getStatusCode() == 200) {
List<KeyValueItem> list = response.body().getResults();
for(int i = 0; i < list.size(); i++) {
KeyValueItem kvi = list.get(i);
// do whatever you want with kvi object
}
}
}
public void onResponse(Call<RestServiceResponse> call, Response<RestServiceResponse> response) {
Log.i("ddsddsadsa", String.valueOf(response.code()));
RestServiceResponse restServiceResponse = response.body();
if(restServiceResponse.getStatusCode() == 200){
List<KeyValueItem> list = response.body().getResults();
for(KeyValueItem keyValueItem : list) {
String key = keyValueItem.getKey();
String value = keyValueItem.getValue();
Log.i("Keykeykey", key);
}
try {
JSONArray jsonArray = new JSONArray(value);
for(int i = 0; i < jsonArray.length();i++) {
JSONObject obj = jsonArray.getJSONObject(i);
String keykey = obj.getString("Key");
String VAlll = obj.getString("Value");
Log.i("c1111",keykey);
Log.i("c222222", VAlll);
}
} catch (JSONException e) {
e.printStackTrace();
}
}
}else if(restServiceResponse.getErrorMessage() != null){
builder = new AlertDialog.Builder(LoginActivity.this);
builder.setTitle("Error");
builder.setMessage(restServiceResponse.getErrorMessage());
builder.setPositiveButton("Ok",null);
AlertDialog alertDialog = builder.create();
alertDialog.show();
}
}
OK. Btw. i have try this to get my result. and it works!
To answer those about a invalid JSON format maybe because i have changed the value of the JSON so may have some mistake on it.
Below is the final log i get:
74/com.appandus.user.konnect I/Keykeykey: AccessTokens 07-12
17:14:38.177 6274-6274/com.appandus.user.konnect I/c1111: XXXXX 07-12
17:14:38.177 6274-6274/com.appandus.user.konnect I/c222222: BABABA
07-12 17:14:38.177 6274-6274/com.appandus.user.konnect I/c1111: NS/NH
: DIDAIDA 07-12 17:14:38.177 6274-6274/com.appandus.user.konnect
I/c222222: YYYYYY

Retrofit 2.0 how to get deserialised error response.body

I'm using Retrofit 2.0.0-beta1.
In tests i have an alternate scenario and expect error HTTP 400
I would like to have retrofit.Response<MyError> response
but response.body() == null
MyError is not deserialised - i see it only here
response.errorBody().string()
but it doesn't give me MyError as object
I currently use a very easy implementation, which does not require to use converters or special classes. The code I use is the following:
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
DialogHelper.dismiss();
if (response.isSuccessful()) {
// Do your success stuff...
} else {
try {
JSONObject jObjError = new JSONObject(response.errorBody().string());
Toast.makeText(getContext(), jObjError.getJSONObject("error").getString("message"), Toast.LENGTH_LONG).show();
} catch (Exception e) {
Toast.makeText(getContext(), e.getMessage(), Toast.LENGTH_LONG).show();
}
}
}
A point to note here is that response.errorBody().string() will return the correct value only once. If you call it again, it will return an empty string. So in case you want to reuse it, store the value in a variable with the first call.
There is a way to get the error body string from the response without making it empty on the next call, by rolling your own implementation of toString() that does not update the errorBody Buffer's read-pointer.
See this answer for more info.
ErrorResponse is your custom response object
Kotlin
val gson = Gson()
val type = object : TypeToken<ErrorResponse>() {}.type
var errorResponse: ErrorResponse? = gson.fromJson(response.errorBody()!!.charStream(), type)
Java
Gson gson = new Gson();
Type type = new TypeToken<ErrorResponse>() {}.getType();
ErrorResponse errorResponse = gson.fromJson(response.errorBody.charStream(),type);
I solved it by:
if(!response.isSuccessful()){
Gson gson = new Gson();
MyErrorMessage message=gson.fromJson(response.errorBody().charStream(),MyErrorMessage.class);
if(message.getCode()==ErrorCode.DUPLICATE_EMAIL_ID_CODE){
//DO Error Code specific handling
}else{
//DO GENERAL Error Code Specific handling
}
}
MyErrorMessage Class:
public class MyErrorMessage {
private int code;
private String message;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
It's actually very straight forward.
Kotlin:
val jsonObj = JSONObject(response.errorBody()!!.charStream().readText())
responseInterface.onFailure(jsonObj.getString("msg"))
Java:
if(response.errorBody()!=null){
JSONObject jsonObj = new JSONObject(TextStreamsKt.readText(response.errorBody().charStream()));
responseInterface.onFailure(jsonObj.getString("msg"));
}else{
responseInterface.onFailure("you might want to return a generic error message.");
}
Tested on retrofit:2.5.0.
Read the text from the charStream which will give you a String, then parse to JSONObject.
Adios.
In Retrofit 2.0 beta2 this is the way that I'm getting error responses:
Synchronous
try {
Call<RegistrationResponse> call = backendServiceApi.register(data.in.account, data.in.password,
data.in.email);
Response<RegistrationResponse> response = call.execute();
if (response != null && !response.isSuccess() && response.errorBody() != null) {
Converter<ResponseBody, BasicResponse> errorConverter =
MyApplication.getRestClient().getRetrofitInstance().responseConverter(BasicResponse.class, new Annotation[0]);
BasicResponse error = errorConverter.convert(response.errorBody());
//DO ERROR HANDLING HERE
return;
}
RegistrationResponse registrationResponse = response.body();
//DO SUCCESS HANDLING HERE
} catch (IOException e) {
//DO NETWORK ERROR HANDLING HERE
}
Asynchronous
Call<BasicResponse> call = service.loadRepo();
call.enqueue(new Callback<BasicResponse>() {
#Override
public void onResponse(Response<BasicResponse> response, Retrofit retrofit) {
if (response != null && !response.isSuccess() && response.errorBody() != null) {
Converter<ResponseBody, BasicResponse> errorConverter =
retrofit.responseConverter(BasicResponse.class, new Annotation[0]);
BasicResponse error = errorConverter.convert(response.errorBody());
//DO ERROR HANDLING HERE
return;
}
RegistrationResponse registrationResponse = response.body();
//DO SUCCESS HANDLING HERE
}
#Override
public void onFailure(Throwable t) {
//DO NETWORK ERROR HANDLING HERE
}
});
Update for Retrofit 2 beta3:
Synchronous - not changed
Asynchronous - Retrofit parameter was removed from onResponse
Call<BasicResponse> call = service.loadRepo();
call.enqueue(new Callback<BasicResponse>() {
#Override
public void onResponse(Response<BasicResponse> response) {
if (response != null && !response.isSuccess() && response.errorBody() != null) {
Converter<ResponseBody, BasicResponse> errorConverter =
MyApplication.getRestClient().getRetrofitInstance().responseConverter(BasicResponse.class, new Annotation[0]);
BasicResponse error = errorConverter.convert(response.errorBody());
//DO ERROR HANDLING HERE
return;
}
RegistrationResponse registrationResponse = response.body();
//DO SUCCESS HANDLING HERE
}
#Override
public void onFailure(Throwable t) {
//DO NETWORK ERROR HANDLING HERE
}
});
Create a model of the Error response & user Gson to convert the response to it. This will just work fine.
APIError.java
public class APIError {
private String message;
public String getMessage() {
return message;
}
}
MainActivity.java (inside request onResponse)
if (response.isSuccessful()) {
// Do your success stuff...
} else {
APIError message = new Gson().fromJson(response.errorBody().charStream(), APIError.class);
Toast.makeText(MainActivity.this, "" + message.getMessage(), Toast.LENGTH_SHORT).show();
}
If you use Kotlin another solution could be just create extension function for Response class:
inline fun <reified T>Response<*>.parseErrJsonResponse(): T?
{
val moshi = MyCustomMoshiBuilder().build()
val parser = moshi.adapter(T::class.java)
val response = errorBody()?.string()
if(response != null)
try {
return parser.fromJson(response)
} catch(e: JsonDataException) {
e.printStackTrace()
}
return null
}
Usage
val myError = response.parseErrJsonResponse<MyErrorResponse>()
if(myError != null) {
// handle your error logic here
// ...
}
#Override
public void onResponse(Call<Void> call, retrofit2.Response<Void> response) {
if (response.isSuccessful()) {
//Do something if response is ok
} else {
JsonParser parser = new JsonParser();
JsonElement mJson = null;
try {
mJson = parser.parse(response.errorBody().string());
Gson gson = new Gson();
MyError errorResponse = gson.fromJson(mJson, MyError.class);
} catch (IOException ex) {
ex.printStackTrace();
}
}
In https://stackoverflow.com/a/21103420/2914140 and https://futurestud.io/tutorials/retrofit-2-simple-error-handling this variant is shown for Retrofit 2.1.0.
call.enqueue(new Callback<MyResponse>() {
#Override
public void onResponse(Call<MyResponse> call, Response<MyResponse> response) {
if (response.isSuccessful()) {
...
} else {
Converter<ResponseBody, MyError> converter
= MyApplication.getRetrofit().responseBodyConverter(
MyError.class, new Annotation[0]);
MyError errorResponse = null;
try {
errorResponse = converter.convert(response.errorBody());
} catch (IOException e) {
e.printStackTrace();
}
}
}
if(!response.isSuccessful()) {
StringBuilder error = new StringBuilder();
try {
BufferedReader bufferedReader = null;
if (response.errorBody() != null) {
bufferedReader = new BufferedReader(new InputStreamReader(
response.errorBody().byteStream()));
String eLine = null;
while ((eLine = bufferedReader.readLine()) != null) {
error.append(eLine);
}
bufferedReader.close();
}
} catch (Exception e) {
error.append(e.getMessage());
}
Log.e("Error", error.toString());
}
I did it this way for asynchronous calls using Retrofit 2.0-beta2:
#Override
public void onResponse(Response<RegistrationResponse> response,
Retrofit retrofit) {
if (response.isSuccess()) {
// Do success handling here
} else {
try {
MyError myError = (MyError)retrofit.responseConverter(
MyError.class, MyError.class.getAnnotations())
.convert(response.errorBody());
// Do error handling here
} catch (IOException e) {
e.printStackTrace();
}
}
}
I was facing same issue. I solved it with retrofit. Let me show this...
If your error JSON structure are like
{
"error": {
"status": "The email field is required."
}
}
My ErrorRespnce.java
public class ErrorResponse {
#SerializedName("error")
#Expose
private ErrorStatus error;
public ErrorStatus getError() {
return error;
}
public void setError(ErrorStatus error) {
this.error = error;
}
}
And this my Error status class
public class ErrorStatus {
#SerializedName("status")
#Expose
private String status;
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
}
Now we need a class which can handle our json.
public class ErrorUtils {
public static ErrorResponse parseError (Response<?> response){
Converter<ResponseBody , ErrorResponse> converter = ApiClient.getClient().responseBodyConverter(ErrorResponse.class , new Annotation[0]);
ErrorResponse errorResponse;
try{
errorResponse = converter.convert(response.errorBody());
}catch (IOException e){
return new ErrorResponse();
}
return errorResponse;
}
}
Now we can check our response in retrofit api call
private void registrationRequest(String name , String email , String password , String c_password){
final Call<RegistrationResponce> registrationResponceCall = apiInterface.getRegistration(name , email , password , c_password);
registrationResponceCall.enqueue(new Callback<RegistrationResponce>() {
#Override
public void onResponse(Call<RegistrationResponce> call, Response<RegistrationResponce> response) {
if (response.code() == 200){
}else if (response.code() == 401){
ErrorResponse errorResponse = ErrorUtils.parseError(response);
Toast.makeText(MainActivity.this, ""+errorResponse.getError().getStatus(), Toast.LENGTH_SHORT).show();
}
}
#Override
public void onFailure(Call<RegistrationResponce> call, Throwable t) {
}
});
}
That's it now you can show your Toast
Here is elegant solution using Kotlin extensions:
data class ApiError(val code: Int, val message: String?) {
companion object {
val EMPTY_API_ERROR = ApiError(-1, null)
}
}
fun Throwable.getApiError(): ApiError? {
if (this is HttpException) {
try {
val errorJsonString = this.response()?.errorBody()?.string()
return Gson().fromJson(errorJsonString, ApiError::class.java)
} catch (exception: Exception) {
// Ignore
}
}
return EMPTY_API_ERROR
}
and usage:
showError(retrofitThrowable.getApiError()?.message)
There are many valid answers already. This is just an addition for a use case, when you need to consume same Retrofit response more than once. Neither of below can be used, as you can read response body only once, as it will be closed afterwards and you will get null each next time, when you try to read from the same response object:
response()?.errorBody()?.charStream()?.readText()
response()?.errorBody()?.string()
Instead, you can get read-only copy of response string (while the response itself can be passed over and eventually consumed later):
response()?.errorBody()?.source()?.buffer?.snapshot()?.utf8()
This way you do not need a Retrofit instance if you only are injecting a service created from Retrofit.
public class ErrorUtils {
public static APIError parseError(Context context, Response<?> response) {
APIError error = new APIError();
try {
Gson gson = new Gson();
error = gson.fromJson(response.errorBody().charStream(), APIError.class);
} catch (Exception e) {
Toast.makeText(context, e.getMessage(), Toast.LENGTH_LONG).show();
}
if (TextUtils.isEmpty(error.getErrorMessage())) {
error.setError(response.raw().message());
}
return error;
}
}
Use it like this:
if (response.isSuccessful()) {
...
} else {
String msg = ErrorUtils.parseError(fragment.getActivity(), response).getError(); // would be from your error class
Snackbar.make(someview, msg, Snackbar.LENGTH_LONG).show();
}
}
if your error response is a string you can deserialize it by using the following kotlin code :
val errorString = response.errorBody()?.byteStream()?.bufferedReader().use { it?.readText() } // defaults to UTF-8
This seems to be the problem when you use OkHttp along with Retrofit, so either you can remove OkHttp or use code below to get error body:
if (!response.isSuccessful()) {
InputStream i = response.errorBody().byteStream();
BufferedReader r = new BufferedReader(new InputStreamReader(i));
StringBuilder errorResult = new StringBuilder();
String line;
try {
while ((line = r.readLine()) != null) {
errorResult.append(line).append('\n');
}
} catch (IOException e) {
e.printStackTrace();
}
}
Tested and works
public BaseModel parse(Response<BaseModel> response , Retrofit retrofit){
BaseModel error = null;
Converter<ResponseBody, BaseModel> errorConverter =
retrofit.responseBodyConverter(BaseModel.class, new Annotation[0]);
try {
if (response.errorBody() != null) {
error = errorConverter.convert(response.errorBody());
}
} catch (IOException e) {
e.printStackTrace();
}
return error;
}
For people using Kotlin with Moshi and coroutines, this is what I did:
Error data class
#JsonClass(generateAdapter = true)
data class ApiResponseNoData(
val exito: Int,
val error: String?
)
Extension
fun ResponseBody.getApiError(): String? {
return try {
Moshi
.Builder()
.build()
.adapter(ApiResponseNoData::class.java)
.fromJson(string())
?.error
}catch(e: Exception) { null }
}
ViewModel
fun test() {
viewModelScope.launch(Dispatchers.IO) {
val response = repository.test()
withContext(Dispatchers.Main) {
if(response.isSuccessful) {
...
}else{
val errorMsg = response.errorBody()?.getApiError() ?: "Unexpected error occurred"
...
]
}
}
}
json response
{
"success": false,
"status_code": 32,
"status_message": "Email not verified: Your email address has not been verified."
}
Error class
data class ResponseError(
#SerializedName("status_code")
val statusCode: Int,
#SerializedName("status_message")
val statusMessage: String,
#SerializedName("success")
val success: Boolean
)
get error message
fun <T : Any> getResultOrError(response: Response<T>): T? {
if (response.isSuccessful) {
return response.body()
} else {
try {
val responseError = Gson().fromJson(
response.errorBody()?.string(),
ResponseError::class.java
)
throw Throwable(responseError.statusMessage)
} catch (e: Exception) {
throw Throwable("Unknown error")
}
}
}
solved it by:
Converter<MyError> converter =
(Converter<MyError>)JacksonConverterFactory.create().get(MyError.class);
MyError myError = converter.fromBody(response.errorBody());
try{
ResponseBody response = ((HttpException) t).response().errorBody();
JSONObject json = new JSONObject( new String(response.bytes()) );
errMsg = json.getString("message");
}catch(JSONException e){
return t.getMessage();
}
catch(IOException e){
return t.getMessage();
}
In Kotlin:
val call = APIClient.getInstance().signIn(AuthRequestWrapper(AuthRequest("1234567890z", "12341234", "nonce")))
call.enqueue(object : Callback<AuthResponse> {
override fun onResponse(call: Call<AuthResponse>, response: Response<AuthResponse>) {
if (response.isSuccessful) {
} else {
val a = object : Annotation{}
val errorConverter = RentalGeekClient.getRetrofitInstance().responseBodyConverter<AuthFailureResponse>(AuthFailureResponse::class.java, arrayOf(a))
val authFailureResponse = errorConverter.convert(response.errorBody())
}
}
override fun onFailure(call: Call<AuthResponse>, t: Throwable) {
}
})
errorBody values should set APIError object in Retrofit. So that, you can use the below code structure.
public class APIErrorUtils {
public static APIError parseError(Response<?> response) {
Converter<ResponseBody, APIError> converter = API.getClient().responseBodyConverter(APIError.class, new Annotation[0]);
APIError error;
try {
error = converter.convert(response.errorBody());
Log.d("SERVICELOG", "****************************************************");
Log.d("SERVICELOG", "***** SERVICE LOG");
Log.d("SERVICELOG", "***** TIMESTAMP: " + String.valueOf(error.getTimestamp()));
Log.d("SERVICELOG", "***** STATUS: " + String.valueOf(error.getStatus()));
Log.d("SERVICELOG", "***** ERROR: " + error.getError());
Log.d("SERVICELOG", "***** MESSAGE: " + error.getMessage());
Log.d("SERVICELOG", "***** PATH: " + error.getPath());
Log.d("SERVICELOG", "****************************************************");
} catch (IOException e) {
return new APIError();
}
return error;
}
}
APIError error = APIErrorUtils.parseError(response);
if (error.getStatus() == 400) {
....
}
val error = JSONObject(callApi.errorBody()?.string() as String)
CustomResult.OnError(CustomNotFoundError(userMessage = error["userMessage"] as String))
open class CustomError (
val traceId: String? = null,
val errorCode: String? = null,
val systemMessage: String? = null,
val userMessage: String? = null,
val cause: Throwable? = null
)
open class ErrorThrowable(
private val traceId: String? = null,
private val errorCode: String? = null,
private val systemMessage: String? = null,
private val userMessage: String? = null,
override val cause: Throwable? = null
) : Throwable(userMessage, cause) {
fun toError(): CustomError = CustomError(traceId, errorCode, systemMessage, userMessage, cause)
}
class NetworkError(traceId: String? = null, errorCode: String? = null, systemMessage: String? = null, userMessage: String? = null, cause: Throwable? = null):
CustomError(traceId, errorCode, systemMessage, userMessage?: "Usted no tiene conexión a internet, active los datos", cause)
class HttpError(traceId: String? = null, errorCode: String? = null, systemMessage: String? = null, userMessage: String? = null, cause: Throwable? = null):
CustomError(traceId, errorCode, systemMessage, userMessage, cause)
class UnknownError(traceId: String? = null, errorCode: String? = null, systemMessage: String? = null, userMessage: String? = null, cause: Throwable? = null):
CustomError(traceId, errorCode, systemMessage, userMessage?: "Unknown error", cause)
class CustomNotFoundError(traceId: String? = null, errorCode: String? = null, systemMessage: String? = null, userMessage: String? = null, cause: Throwable? = null):
CustomError(traceId, errorCode, systemMessage, userMessage?: "Data not found", cause)`
Error body handling in kotlin Android
catch (cause: Throwable) {
when (cause) {
is HttpException -> {
try {
val YourErrorResponseClassObj = Gson().fromJson(cause.response()?.errorBody()?.charStream(), YourErrorResponseClass::class.java)
} catch (e: Exception) {
}
}
else -> {
//Other errors like Network ...
}
}
}
very simple. and this save my life ever
public static void displayApiResponseErrorBody(Response<?> response)
{
InputStream i = response.errorBody().byteStream();
BufferedReader r = new BufferedReader(new InputStreamReader(i));
StringBuilder errorResult = new StringBuilder();
String line;
try {
while ((line = r.readLine()) != null)
{
errorResult.append(line).append('\n');
}
Log.d("API_RESPONSE_ERROR_BODY",String.valueOf(errorResult));
System.out.println(errorResult);
} catch (IOException e) {
e.printStackTrace();
}
}
In case of retrofit error Response, You can get body using error.getResponse(), Here is the example.
#Override
public void failure(RetrofitError error){
if(error.getResponse().getStatus()==201){
LogUtil.INSTANCE.debug("Success : " + error.toString());
callback.success(error.getResponse().getBody);
}else{
LogUtil.INSTANCE.debug("failure: " + error.toString());
callback.failure(error);
}
}
In Kotlin I solved it creating a custom ResponseBody generic extension function function that converts the response body to a JSONObject. then you can use gson to customize the error response body with your custom Error Data Class.
inline fun <reified T> ResponseBody.getErrorObject(): T {
val gson = Gson()
val jsonObject = JSONObject(charStream().readText())
return gson.fromJson(jsonObject.toString(), T::class.java)
}
You can then customer the error response to your custom class. For this I'm using an example
data class LoginError(
val error: Error,
val message: String,
val success: Boolean
)
data class Error(
val error: String,
val status: Int
)
then use the extension function this way
val error = state.errorBody.getErrorObject<LoginError>()
the state.errorBody is my error response from retrofit of type ResponseBody
val reader = BufferedReader(response.errorBody()?.source().inputStream().reader())
val content = StringBuilder()
reader.use { readerBuffer ->
var line = readerBuffer.readLine()
while (line != null) {
content.append(line)
line = readerBuffer.readLine()
}
}
Gson().fromJson(content.toString(), ResponseData::class.java)

Gson deserializer support multiple types

I want to deserialize a JSON response but I'm not sure about the format. The format can vary in each case. For example the response contains a field named "error" which may be false (boolean) or an object that describes the error eg. "error": { "code": xxx , "description":"etc"}
How should I implement a class that covers both cases? Is there any way to do this?
Thanks
I would prefer using a TypeAdapter for your case:
private static class Error {
private boolean hasError;
private int code;
private String description;
}
private static class ErrorTypeAdapter extends TypeAdapter<Error> {
#Override
public Error read(JsonReader jsonReader) throws IOException {
Error response = null;
jsonReader.beginObject();
while (jsonReader.hasNext()) {
String currentJsonName = jsonReader.nextName();
if("error".equals(currentJsonName)) {
response = new Error();
try {
response.hasError = jsonReader.nextBoolean();
} catch (Exception e) {
response.hasError = true;
jsonReader.beginObject();
}
} else if("code".equals(currentJsonName)) {
response.code = jsonReader.nextInt();
} else if ("description".equals(currentJsonName)) {
response.description = jsonReader.nextString();
}
}
if(response.hasError) {
jsonReader.endObject();
}
jsonReader.endObject();
return response;
}
#Override
public void write(JsonWriter jsonWriter, Error response)
throws IOException {
jsonWriter.beginObject();
jsonWriter.name("hasError").value(response.hasError);
jsonWriter.name("code").value(response.code);
jsonWriter.name("description").value(response.description);
jsonWriter.endObject();
}
}
To test it you can use:
String val1 = "{\"error\": {\"code\": 1 , \"description\":\"etc\"}}";
String val2 = "{\"error\": false}";
final GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Error.class, new ErrorTypeAdapter());
gsonBuilder.setPrettyPrinting();
final Gson gson = gsonBuilder.create();
gson.fromJson(val1, Error.class);
gson.fromJson(val2, Error.class);
You can read more about TypeAdapters here and also some great examples here.

Categories

Resources