Retrofit 2.0 how to get deserialised error response.body - java

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)

Related

TupleTag not found in DoFn

I have a DoFn that is supposed to split input into two separate PCollections. The pipeline builds and runs up until it is time to output in the DoFn, and then I get the following exception:
"java.lang.IllegalArgumentException: Unknown output tag Tag<edu.mayo.mcc.cdh.pipeline.PubsubToAvro$PubsubMessageToArchiveDoFn$2.<init>:219#2587af97b4865538>
at org.apache.beam.vendor.guava.v26_0_jre.com.google.common.base.Preconditions.checkArgument(Preconditions.java:216)...
If I declare the TupleTags I'm using in the ParDo, I get that error, but if I declare them outside of the ParDo I get a syntax error saying the OutputReceiver can't find the tags. Below is the apply and the ParDo/DoFn:
PCollectionTuple results = (messages.apply("Map to Archive", ParDo.of(new PubsubMessageToArchiveDoFn()).withOutputTags(noTag, TupleTagList.of(medaPcollection))));
PCollection<AvroPubsubMessageRecord> medaPcollectionTransformed = results.get(medaPcollection);
PCollection<AvroPubsubMessageRecord> noTagPcollectionTransformed = results.get(noTag);
static class PubsubMessageToArchiveDoFn extends DoFn<PubsubMessage, AvroPubsubMessageRecord> {
final TupleTag<AvroPubsubMessageRecord> medaPcollection = new TupleTag<AvroPubsubMessageRecord>(){};
final TupleTag<AvroPubsubMessageRecord> noTag = new TupleTag<AvroPubsubMessageRecord>(){};
#ProcessElement
public void processElement(ProcessContext context, MultiOutputReceiver out) {
String appCode;
PubsubMessage message = context.element();
String msgStr = new String(message.getPayload(), StandardCharsets.UTF_8);
try {
JSONObject jsonObject = new JSONObject(msgStr);
LOGGER.info("json: {}", jsonObject);
appCode = jsonObject.getString("app_code");
LOGGER.info(appCode);
if(appCode == "MEDA"){
LOGGER.info("Made it to MEDA tag");
out.get(medaPcollection).output(new AvroPubsubMessageRecord(
message.getPayload(), message.getAttributeMap(), context.timestamp().getMillis()));
} else {
LOGGER.info("Made it to default tag");
out.get(noTag).output(new AvroPubsubMessageRecord(
message.getPayload(), message.getAttributeMap(), context.timestamp().getMillis()));
}
} catch (Exception e) {
LOGGER.info("Error Processing Message: {}\n{}", msgStr, e);
}
}
}
Can you try without MultiOutputReceiver out parameter in the processElement method ?
Outputs are then returned with context.output with passing element and corresponding TupleTag.
Your example only with context :
static class PubsubMessageToArchiveDoFn extends DoFn<PubsubMessage, AvroPubsubMessageRecord> {
final TupleTag<AvroPubsubMessageRecord> medaPcollection = new TupleTag<AvroPubsubMessageRecord>(){};
final TupleTag<AvroPubsubMessageRecord> noTag = new TupleTag<AvroPubsubMessageRecord>(){};
#ProcessElement
public void processElement(ProcessContext context) {
String appCode;
PubsubMessage message = context.element();
String msgStr = new String(message.getPayload(), StandardCharsets.UTF_8);
try {
JSONObject jsonObject = new JSONObject(msgStr);
LOGGER.info("json: {}", jsonObject);
appCode = jsonObject.getString("app_code");
LOGGER.info(appCode);
if(appCode == "MEDA"){
LOGGER.info("Made it to MEDA tag");
context.output(medaPcollection, new AvroPubsubMessageRecord(
message.getPayload(), message.getAttributeMap(), context.timestamp().getMillis()));
} else {
LOGGER.info("Made it to default tag");
context.output(noTag, new AvroPubsubMessageRecord(
message.getPayload(), message.getAttributeMap(), context.timestamp().getMillis()));
}
} catch (Exception e) {
LOGGER.info("Error Processing Message: {}\n{}", msgStr, e);
}
}
I also show you an example that works for me :
public class WordCountFn extends DoFn<String, Integer> {
private final TupleTag<Integer> outputTag = new TupleTag<Integer>() {};
private final TupleTag<Failure> failuresTag = new TupleTag<Failure>() {};
#ProcessElement
public void processElement(ProcessContext ctx) {
try {
// Could throw ArithmeticException.
final String word = ctx.element();
ctx.output(1 / word.length());
} catch (Throwable throwable) {
final Failure failure = Failure.from("step", ctx.element(), throwable);
ctx.output(failuresTag, failure);
}
}
public TupleTag<Integer> getOutputTag() {
return outputTag;
}
public TupleTag<Failure> getFailuresTag() {
return failuresTag;
}
}
In my first output (good case), no need to pass the TupleTag ctx.output(1 / word.length());
For my second output (failure case), I pass the Failure tag with the corresponding element.
I was able to get around this by making my ParDo an anonymous function instead of a class. I put the whole function inline and had no problem finding the output tags after I did that. Thanks for the suggestions!

How do I parse Volley JSON Object with Gson?

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.

How to use custom converter?

I want to parse time from
<monday>
<item>
<time>00:00:00</time>
</item>
...
</monday>
as long
I defiend items as
#Root(strict = false)
private static class Item {
#Element(name = "time")
#Convert(TimeConverter.class)
private Long time;
}
My Converter
public class TimeConverter implements org.simpleframework.xml.convert.Converter<Long> {
SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss");
#Override
public Long read(InputNode node) throws Exception {
try {
String value = node.getValue();
return df.parse(value).getTime();
} catch (Exception e) {
Log.e("mcheck", "read: ", e);
return 0L;
}
}
#Override
public void write(OutputNode node, Long value) throws Exception {
try {
String v = df.format(new Date(value));
node.setValue(v);
} catch (Exception e) {
Log.e("mcheck", "write: ", e);
}
}
}
However when I parse it I receive
retrofit.RetrofitError: java.lang.NumberFormatException: Invalid long: "00:00:00"
As this exception is not caught in my try-catch blocks in converter I assume that parser does not visit converter at all.
My retrofit 1.9 call
OkHttpClient httpClient = new OkHttpClient();
RestAdapter.Builder builder = new RestAdapter.Builder();
builder.setEndpoint(url);
builder.setLogLevel(RestAdapter.LogLevel.FULL);
builder.setConverter(new SimpleXMLConverter());
builder.setClient(new OkClient(httpClient));
RestAdapter restAdapter = builder.build();
ChansonApi api = restAdapter.create(ChansonApi.class);
api.getStreamProgram(new Callback<StreamProgram>() {
#Override
public void success(StreamProgram streamProgram, Response response) {
if(streamProgram!=null){
Log.e("mcheck", "success: "+streamProgram.getProgram());
}
}
#Override
public void failure(RetrofitError error) {
Log.e("mcheck", "failure: ",error);
}
});
The problem was in proguard settings. I had to add TimeConverter to -keep class list

java.io.EOFException: End of input at line 1 column 1 path $ in Gson parser

I'm parsing a JSON string by using Gson and Retrofit. I have this JSON string:
{"message":["Email has already been taken"]}
I get the below exception still and don't know why:
java.io.EOFException: End of input at line 1 column 1 path $
at com.google.gson.stream.JsonReader.nextNonWhitespace(JsonReader.java:1393)
at com.google.gson.stream.JsonReader.doPeek(JsonReader.java:549)
at com.google.gson.stream.JsonReader.peek(JsonReader.java:425)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:205)
at com.google.gson.TypeAdapter.fromJson(TypeAdapter.java:260)
at com.google.gson.TypeAdapter.fromJson(TypeAdapter.java:273)
People who know how to get the value of message field please help me.
BaseApiDto.java
public class BaseApiDto {
#SerializedName("message")
public String[] message;
public String getError() {
return message[0];
}
}
HandErrorUtils.java
public static void handleError(FragmentActivity activity, Throwable e) {
String msg = null;
if(e instanceof HttpException){
// Error message in json
Gson gson = new Gson();
TypeAdapter<BaseApiDto> adapter = gson.getAdapter(BaseApiDto.class);
ResponseBody body = ((HttpException) e).response().errorBody();
// Status code
HttpException httpException = (HttpException) e;
int statusCode = httpException.code();
if (statusCode == 500) {
showErrorDialog(activity, activity.getString(R.string.dialog_msg_error_401), true);
} else if (statusCode == 401) {
showErrorDialog(activity, activity.getString(R.string.dialog_msg_error_401), true);
} else {
try {
Timber.w("body.string() " + body.string());
// TODO : EXCEPTION HAPPEN IN HERE
BaseApiDto errorDto = adapter.fromJson(body.string());
msg = errorDto.getError();
Timber.w("msg " + msg);
} catch (Exception ex) {
// TODO : EXCEPTION HAPPEN IN HERE
ex.printStackTrace();
}
showErrorDialog(activity, msg, false);
}
}
}
UPDATE I assign body.toString() to variable, somehow it worked.
String response = body.string();
BaseApiDto errorDto = adapter.fromJson(response);
It worked because I didn't call body.string() twice.
I assign body.toString() to variable, somehow it worked.
String response = body.string();
BaseApiDto errorDto = adapter.fromJson(response);

Android: how to create post xml-rpc request with custom http header & content type

I want to creat xml-rpc POST request, and pass 2 parameters "application_name" & "key", and change content type to "application/x-www-form-urlencoded" like my code below.
try {
XMLRPCClient oneTimeKeyClient = new XMLRPCClient(new URL(URL_REQUEST_SAMPLE), XMLRPCClient.FLAGS_DEFAULT_TYPE_STRING);
oneTimeKeyClient.setCustomHttpHeader("X-HTTP-METHOD-OVERRIDE", "POST");
// oneTimeKeyClient.setCustomHttpHeader("Content-Type", "application/x-www-form-urlencoded");
HashMap<String, String> oneTimeKeyParam = new HashMap<>();
oneTimeKeyParam.put("application_name", "hello_app");
oneTimeKeyParam.put("key", "bb5eb953d3b41dcf59f4669d98f8e14782ed83133be772956b");
Vector<Object> params = new Vector<Object>();
params.add(oneTimeKeyParam);
oneTimeKeyClient.callAsync(new XMLRPCCallback() {
#Override
public void onResponse(long id, Object response) {
try {
result = ((Map) response).get(NAME_ONE_TIME_KEY).toString();
} catch (Exception e) {
Timber.e("onParseError %s", e.getMessage());
DialogUtil.showLoginErrorDialog(getSupportFragmentManager());
}
}
#Override
public void onError(long id, XMLRPCException error) {
Timber.e("onError %s", error.getMessage());
DialogUtil.showLoginErrorDialog(getSupportFragmentManager());
}
#Override
public void onServerError(long id, XMLRPCServerException error) {
Timber.e("onServerError %s", error.getMessage());
DialogUtil.showLoginErrorDialog(getSupportFragmentManager());
}
}, "", params);
} catch (Exception e) {
Timber.e("onError %s", e.getMessage());
DialogUtil.showLoginErrorDialog(getSupportFragmentManager());
}
I got error " onServerError APPLICATION_NAME must record not exists."
I using aXMLRPC library https://github.com/gturri/aXMLRPC .
Which library do you recommend ?
Can I use Retrofit to make xml-rpc request ?
Thanks for any help
You just use retrofit like this:
#FormUrlEncoded
#POST("onetime_key")
Observable<OneTimeKeyRes> requestOneTimeKey(#Field("application_name") String applicationName, #Field("key") String key);
You must add SimpleXmlConverter:
Retrofit retrofit = new Retrofit.Builder()
.client(builder.build())
.baseUrl(YOUR_BASE_URL)
.addConverterFactory(SimpleXmlConverterFactory.create())
.build();

Categories

Resources