Handle inconsistent API response with Retrofit - java

I'm facing an issue in new project. I'm connecting to API that could response two different data types in one response based on some server logic. I'm using Retrofit on Android and I was wondering if there's some "easy" way to handle that cases before retrofit object parse, eg. some kind of parser/serializer that would check what type has specific JSON field? I dunno.
Here are possible responses:
error response:
{
"ReturnCode": "error",
"ReturnCodeNumber": 444,
"ReturnMessage": "Invalid Request",
"ReturnData": ""
}
data response:
{
"ReturnCode": "ok",
"ReturnCodeNumber": 0,
"ReturnMessage": "success",
"ReturnData": [
{
}
]
}
Retrofit API request:
#FormUrlEncoded
#POST("url")
Observable<ApiResponse<List<Data>>> requestData()
API response class has exposed fields of above response and parameterized T for returnData.
So is it possible to somehow wrap it in some serializer class?

You will have to write custom deserializer or register a type adapter as explained in this -
https://sites.google.com/site/gson/gson-user-guide#TOC-Custom-Serialization-and-Deserialization

Try to do by using TypeAdapterFactory. Sample of that class as shown below.
public class ItemTypeAdapterFactory implements TypeAdapterFactory {
#Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);
return new TypeAdapter<T>() {
public void write(JsonWriter out, T value) throws IOException {
delegate.write(out, value);
}
public T read(JsonReader in) throws IOException {
JsonElement jsonElement = elementAdapter.read(in);
if (jsonElement.isJsonObject()) {
JsonObject jsonObject = jsonElement.getAsJsonObject();
if (jsonObject.has("data") && jsonObject.get("data").isJsonObject()) {
jsonElement = jsonObject.get("data");
}
}
return delegate.fromJsonTree(jsonElement);
}
}.nullSafe();
}
}
And this Gson into RestAdapter
final Gson gson = new GsonBuilder().registerTypeAdapterFactory(new ItemTypeAdapterFactory()).create();
final Client client = new OkClient(new OkHttpClient());
final RestAdapter restAdapter = new RestAdapter.Builder().setClient(client).setLogLevel(RestAdapter.LogLevel.FULL).setConverter(new GsonConverter(gson)).setEndpoint(context.getString(R.string.base_url)).build();

Related

Java consuming restful api with httpclient. Having trouble with Jackson json MismatchedInputException

This is my first time using jackson/consuming apis/httpclient. I'm getting this error com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type java.util.ArrayList<WallHaven> from Object value (token JsonToken.START_OBJECT) . The api I'm trying to consume is https://wallhaven.cc/help/api
try {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.GET()
.uri(URI.create("https://wallhaven.cc/api/v1/w/pkgkkp"))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
ObjectMapper mapper = new ObjectMapper();
List<WallHaven> posts = mapper.readValue(response.body(), new TypeReference<List<WallHaven>>() {
});
posts.forEach(System.out::println);
} catch (Exception e) {
e.printStackTrace();
}
The api json format is https://pastebin.com/tbSaVJ1T
Here's my WallHaven class
public class WallHaven {
public Data data;
public WallHaven(Data data) {
this.data = data;
}
public WallHaven() {
}
#Override
public String toString() {
return "WallHaven{" +
"data=" + data.getPath() +
'}';
}
}
Data contains all the other classes/variables
This is happening because you're trying to deserialize a Json Object into a List in java. The error message explains it by saying that the starting character (JsonToken.START_OBJECT) is the start of a json object not a json array, so you can't deserialize it directly into a List, but should deserialize it into an object.
Try changing:
List<WallHaven> posts = mapper.readValue(response.body(), new TypeReference<List<WallHaven>>())
into
WallHaven post = mapper.readValue(response.body(), new TypeReference<WallHaven>())

Send JSON in request OkHttp

Friends!
I have a simple HTTP request:
void postRequest(String postUrl,String phone, String message) throws IOException {
OkHttpClient client = new OkHttpClient();
//RequestBody body = RequestBody.create(JSON, postBody);
RequestBody body = new FormBody.Builder()
.add("phone", phone)
.add("message", message)
.build();
Request request = new Request.Builder()
.url(postUrl)
.post(body)
.build();
//System.out.println(request);
client.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
call.cancel();
}
#Override
public void onResponse(Call call, Response response) throws IOException {
Log.d("TAG",response.body().string());
}
});
}
How to properly implement sending a JSON object instead of simple parameters?
My attempts were unsuccessful, so I really need a hint.
The server that will accept JSON is running on AKKA-HTTP.
How do I send a request to this server correctly?
final case class Message(phone: String, message: String, service: String)
implicit val item = jsonFormat3(Message)
val queue: Queue[Message] = Queue()
val addMessage = post {
path("add_message"){
parameters("phone".as[String], "message".as[String], "service".as[String]){
(phone, message, service) => {
queue.enqueue(Message(phone, message, service))
complete("ok")
}
}
}
}
The easiest way to map and serialize your object in JSON format is to use the ObjectMapper class of jackson-databind library.
I personally use it to implement integration tests of RestControllers and it works very well. Here is the utility class I realized, you can take it and use it for your purposes:
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public final class JsonUtils {
public static String json(Object obj) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.writeValueAsString(obj);
}
}
What you need to have is a POJO class which implements Serializable, and then pass the instance of your class to the json method and it will return the JSON format.
You can definitely use it for Android projects. I found many examples where you can add the dependency, but it depends whether you use Gradle or Maven.
Try that out!!!
How do you like this option?
I tried to implement it, but the send fails.
I'm missing an important detail. But I don't understand what it is.
// create your json here
JSONObject jsonObject = new JSONObject();
try {
jsonObject.put("KEY1", "VALUE1");
jsonObject.put("KEY2", "VALUE2");
} catch (JSONException e) {
e.printStackTrace();
}
OkHttpClient client = new OkHttpClient();
MediaType JSON = MediaType.parse("application/json; charset=utf-8");
// put your json here
RequestBody body = RequestBody.create(JSON, jsonObject.toString());
Request request = new Request.Builder()
.url("https://YOUR_URL/")
.post(body)
.build();
Response response = null;
try {
response = client.newCall(request).execute();
String resStr = response.body().string();
} catch (IOException e) {
e.printStackTrace();
}

How to get string response from Retrofit2?

I am doing android, looking for a way to do a super basic http GET/POST request. I keep getting an error:
java.lang.IllegalArgumentException: Unable to create converter for class java.lang.String
Webservice:
public interface WebService {
#GET("/projects")
Call<String> jquery();
}
then in my java:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://jquery.org")
// .addConverterFactory(GsonConverterFactory.create())
.build();
WebService service = retrofit.create(WebService.class);
Call<String> signin = service.jquery();
Toast.makeText(this, signin.toString(), Toast.LENGTH_LONG).show();
I'm literally just trying to query jquery.org/projects with a GET request and return the String that it responds with. What is wrong?
If I try to implement a custom Converter (I've found a few examples online) it complains that I didn't implement the abstract method convert(F), which none of the examples do.
Thanks.
Add Retrofit2 and ScalarsConverterFactory to your Retrofit.Builder.
adapterBuilder = new Retrofit.Builder()
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create());
To use ScalarsCoverter add following dependency to your build graddle
implementation 'com.squareup.retrofit2:converter-scalars:2.9.0'
implementation 'com.squareup.retrofit2:retrofit:2.9.0' //Adding Retrofit2
For API Call use:
Call <String> *****
Android Code :
.enqueue(new Callback<String>() {
#Override
public void onResponse(Call<String> call, Response<String> response) {
Log.i("Response", response.body().toString());
//Toast.makeText()
if (response.isSuccessful()){
if (response.body() != null){
Log.i("onSuccess", response.body().toString());
}else{
Log.i("onEmptyResponse", "Returned empty response");//Toast.makeText(getContext(),"Nothing returned",Toast.LENGTH_LONG).show();
}
}
I take a look at Retrofit library and noticed that it parses response according to the type class inside Call<T>. So you have two option:
1st: create a class according to the response from the server.
2nd: get the response and handle it yourself (Not recommended Retrofit already handles it. So why do you use Retrofit as it is tailored for this job). Anyway instead of Call<String> use Call<ResponseBody> and Call<ResponseBody> signin = service.jquery(); after this put the following
call.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Response<ResponseBody> response, Retrofit retrofit) {
// handle success
String result = response.body().string();
}
#Override
public void onFailure(Throwable t) {
// handle failure
}
});
To get the response as a String, you have to write a converter and pass it when initializing Retrofit.
Here are the steps.
Initializing retrofit.
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(API_URL_BASE)
.addConverterFactory(new ToStringConverterFactory())
.build();
return retrofit.create(serviceClass);
Converter class for converting Retrofit's ResponseBody to String
public class ToStringConverterFactory extends Converter.Factory {
private static final MediaType MEDIA_TYPE = MediaType.parse("text/plain");
#Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
Retrofit retrofit) {
if (String.class.equals(type)) {
return new Converter<ResponseBody, String>() {
#Override
public String convert(ResponseBody value) throws IOException
{
return value.string();
}
};
}
return null;
}
#Override
public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations,
Annotation[] methodAnnotations, Retrofit retrofit) {
if (String.class.equals(type)) {
return new Converter<String, RequestBody>() {
#Override
public RequestBody convert(String value) throws IOException {
return RequestBody.create(MEDIA_TYPE, value);
}
};
}
return null;
}
}
And after executing service.jquery();, signin will contain JSON response.
You can try the following:
build.gradle file:
dependencies {
...
compile 'com.squareup.retrofit2:retrofit:2.0.1'
...
}
WebAPIService:
#GET("/api/values")
Call<String> getValues();
Activity file:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(API_URL_BASE)
.build();
WebAPIService service = retrofit.create(WebAPIService.class);
Call<String> stringCall = service.getValues();
stringCall.enqueue(new Callback<String>() {
#Override
public void onResponse(Call<String> call, Response<String> response) {
Log.i(LOG_TAG, response.body());
}
#Override
public void onFailure(Call<String> call, Throwable t) {
Log.e(LOG_TAG, t.getMessage());
}
});
I have tested with my Web serivce (ASP.Net WebAPI):
public class ValuesController : ApiController
{
public string Get()
{
return "value1";
}
}
Android Logcat out: 04-11 15:17:05.316 23097-23097/com.example.multipartretrofit I/AsyncRetrofit2: value1
Hope it helps!
call.enqueue(new Callback<Object>() {
#Override
public void onResponse(#NonNull Call<Object> call, #NonNull Response<Object> response) {
if (response.isSuccessful()) {
Gson gson = new Gson();
String successResponse = gson.toJson(response.body());
Log.d(LOG_TAG, "successResponse: " + successResponse);
} else {
try {
if (null != response.errorBody()) {
String errorResponse = response.errorBody().string();
Log.d(LOG_TAG, "errorResponse: " + errorResponse);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
#Override
public void onFailure(#NonNull Call<Object> call, #NonNull Throwable t) {
}
});
Just use log level BODY in intercepror:
OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder()....
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
clientBuilder.addNetworkInterceptor(httpLoggingInterceptor);
And you can see body in logcat like:
D/OkHttp: {"blablabla":1,.... }
D/OkHttp: <-- END HTTP (1756-byte body)
This solution for debug only, not for get String directly in code.
thought it might be late to answer just use response.body()?.string() and you'll have your response as a string.
I change the Response type, instead of adding ScalarsConverterFactory like this
interface YourApiService {
#GET("/endpoint")
fun getResponse(): Call<ResponseBody>
}
And then you can get the string object by
val str = response.body().toString()
String body = new String(((TypedByteArray) response.getBody()).getBytes());
if you found
java.lang.ClassCastException: retrofit.client.UrlConnectionClient$TypedInputStream cannot be cast to retrofit.mime.TypedByteArray
then put this in your RestAdapter
.setLogLevel(RestAdapter.LogLevel.FULL)
for Example:
RestAdapter restAdapter = new RestAdapter.Builder()
.setLogLevel(RestAdapter.LogLevel.FULL)
.setEndpoint(Root_url)
.build();

Retrofit POST contains null body

I'm setting up an api client with retrofit, and so far GETs are working fine, but I'm trying to create a new object with a POST, and instead of the object being sent as json, the request body just contains the string "null":
---> HTTP POST http://myapiurl
Content-Type: application/json; charset=UTF-8
Content-Length: 4
null
---> END HTTP (4-byte body)
Here is the method I'm trying to call:
#POST("/Monsters/")
Response new_monster(#Body Monster mon);
And here is how I'm calling it:
#Test
public void testNew_monster() throws Exception {
//create a new monster object, and pass it to the function,
//then query and verify it's in the results?
Monster newmon = new Monster() {
{
name = "deleteme";
description = "created by junit test testNew_monster";
image_url = "http://i.imgur.com/";
created_by = "";
encoded_key = "";
}
};
Response r = client.new_monster(newmon);
assertEquals(201, r.getStatus());
//sleep a couple seconds here?
List<Monster> monsterList = client.monsters();
assertTrue(monsterList.contains(newmon));
}
I am guessing something is going wrong when the object is serialized to json with GSON, but I'm unable to see anything helpful with the debugger during the serialization process...
I'm using GSON version 2.3.1
EDIT: Here's how I'm building the RestAdapter and the client:
static MonSpottingApi GetClient(boolean dbg)
{
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(API_URL)
.build();
if (dbg) restAdapter.setLogLevel(RestAdapter.LogLevel.FULL);
MonSpottingApi client = restAdapter.create(MonSpottingApi.class);
return client;
}
In the test case class:
MonSightingClient.MonSpottingApi client;
#Before
public void setUp() throws Exception {
client = MonSightingClient.GetClient(true);
}
I suspected that the root cause was Gson, so I started making very simple tests and trying to get objects to correctly serialize using toJson(). I think I found a bug in Gson, where it fails if an object is initialized with double-brace syntax:
Using the example class found here
public class GitHubTest {
//fails
#Test
public void testGson1() throws Exception {
GitHubClient.Contributor contrib = new GitHubClient.Contributor() {
{
login = "someguy";
contributions = 99;
}
};
Gson gson = new Gson();
String json = gson.toJson(contrib);
System.out.println("contents of json string: " + json);
assertNotEquals(json, "null");
}
//passes
#Test
public void testGson2() throws Exception {
GitHubClient.Contributor contrib = new GitHubClient.Contributor();
contrib.contributions = 99;
contrib.login = "someguy";
Gson gson = new Gson();
String json = gson.toJson(contrib);
System.out.println("contents of json string: " + json);
assertNotEquals(json, "null");
}
}
Is this a bug in Gson? Or is there some weird subtle Java reason that this happens? (Java is not my strongest language).
I had the same issue, and it looks like a Gson is not happy with the double-brace initialization of the object that is to be converted.
Replacing the double-brace initialization...
AuthenticationRequest req = new AuthenticationRequest() {{
setName("myName");
setPassword("myPass");
}}
with...
AuthenticationRequest req = new AuthenticationRequest();
req.setUserId("myName");
req.setPassword("myPass");
...did the trick.
You have to pass your interface class to create() method of Restadapter. Let's say your interface class is INewService (where you declared new_monster), then GetClient should look like:
public static INewService GetClient(boolean dbg){
RestAdapter restAdapter = new RestAdapter.Builder().
.setEndpoint(API_URL).
.setClient(new OkClient(new OkHttpClient()))
.build();
if (dbg) restAdapter.setLogLevel(RestAdapter.LogLevel.FULL);
return restAdapter.create(INewService.class);
}

Android Volley Server XML Request

I am working on an Android App and using Volley library. The response for some of the requests are in XML format. I searched for Android Volley tutorial on how to deal with XML, but can't seem to find.
The only option at this point for me is:
Use StringRequest to get the Server Response as String
Parse String and traverse it?
Here's the source of Volley:
https://android.googlesource.com/platform/frameworks/volley/+/43950676303ff68b23a8b469d6a534ccd1e08cfc/src/com/android/volley/toolbox
I don't see any class dealing with XML Objects.
Any other advice. Help???
As someone posted in comment, it has been answered here:
Volley library for Android parse xml response?
Volley do not directly provide an XML Object. Your approach of taking the response as String and then inflating to XMLObject is the way I did it.
I made a Class for parsing XML Responses from Server (Combining GsonRequest with Simple). Here's the Class Code Snippet: SimpleXmlRequest
At first it takes the response from Server as String. Then, it uses Simple Serialization tool (http://simple.sourceforge.net/) to inflate the response to XML Object.
gson-xml integrated with GsonRequest may be your answer.
This is something i wrote few weeks back to use with volley. It uses Simple Serialization for xml parsing and is very much similar to GSON.
CODE :
public class XmlGsonRequest<T> extends Request<T> {
public static final int XML_REQUEST = 1;
public static final int GSON_REQUEST = 2;
private Gson mGson;
private Serializer mSerializer;
private final Class<T> mClazz;
private final Listener<T> mListener;
private final int mRequestType;
public XmlGsonRequest(int method, int requestType, String url, Class<T> clazz, Listener<T> listener,
ErrorListener errorListener) {
super(method, url, errorListener);
mClazz = clazz;
mListener = listener;
mRequestType = requestType;
}
#Override
protected void deliverResponse(T response) {
mListener.onResponse(response);
}
#Override
protected Response<T> parseNetworkResponse(NetworkResponse response) {
try {
String source = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
if (mRequestType == XML_REQUEST) {
mSerializer = new Persister();
Reader reader = new StringReader(source);
return Response.success(mSerializer.read(mClazz, reader, false),
HttpHeaderParser.parseCacheHeaders(response));
} else if (mRequestType == GSON_REQUEST) {
mGson = new Gson();
return Response.success(mGson.fromJson(source, mClazz), HttpHeaderParser.parseCacheHeaders(response));
} else {
return null;
}
} catch (UnsupportedEncodingException e) {
return Response.error(new ParseError(e));
} catch (JsonSyntaxException e) {
return Response.error(new ParseError(e));
} catch (Exception e) {
return Response.error(new ParseError(e));
}
}
}

Categories

Resources