Multiple converters for a json response with retrofit - android

I'm trying to create a dynamic response based converter
using retrofit, As for now I have 2 different answers returning from the server - one represents a failure and one represent a valid response How can I try and parse two different objects using the same adapter\callabck?

You can parse it as a java bean if data are json data.
You can use Gson to parse it.
1 Add lib
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
compile 'com.google.code.gson:gson:2.7'
2 Create Retrofit
private Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Api.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
Just add a Gson converter.
For example,
// success
{"retcode":0,"result":{"vfwebqq":"xxxx"}}
// failed
{"retcode":100,"result":{}}
3 Create a bean to receive data.
public class Result {
public String retcode;
public Info result;
public static class Info {
public String vfwebqq;
}
}
4 Then you can return a bean object in retrofit interface.
#GET("xxx")
Result getHome();

Actually I'm not quite in what are you talking about and what exact issue you are facing. But the first thing that pops out of my head is just to provide custom JsonDeserializer. It should look like smth like this :
public class CustomDeserializer implements JsonDeserializer<List<CustomData>> {
#Override
public List<CustomData> deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {
List<CustomData> customDataSet = new ArrayList<>();
Iterator<JsonElement> iterator = ((JsonObject) json).get("data").getAsJsonObject().get(
"records").getAsJsonArray().iterator();
while (iterator.hasNext()) {
JsonElement element = iterator.next();
CustomData customData = ServiceGenerator.mGson.fromJson(element, CustomData.class);
customDataSet.add(customData);
}
return customDataSet;
}
}
That's just a custom parser class example which is applied to RetrofitBuilder just to make life easier(maybe).
Afterwards you need to :
Type listType = new TypeToken<List<CustomData>>() {
}.getType();
mGson = new GsonBuilder().registerTypeAdapter(listType, new CustomDeserializer()).create();
builder =
new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(mGson))
.baseUrl(API_BASE_URL);
retrofit = builder.build();

Based on your question, i believe this site helps with your challenge:
https://futurestud.io/tutorials/retrofit-2-introduction-to-multiple-converters

Related

How to handle JSON response of either a list of objects or a single object using retrofit?

I am calling a REST service (not mine) using retrofit which either returns a list of objects (if there are multiple) or a single object (if one). I was able to find a similar issue here however the suggestion is to change the API which i don't have control of. I also read this thread which seems to be a good approach but is there a way to handle this using Retrofit?
While the answer from #pirho seems to be applicable, I found out a different and simple solution which worked for me. Hopefully it may help others as well.
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(JacksonConverterFactory.create(mapper))
.client(okHttpClient)
.build();
You can get the API response data as Map<String, JsonElement> in response and then parse it based on your requirement directly. As you can check here if JsonElement is JsonArray
for ex:
public fun parseData(val jsonElement:JsonElement){
val gson = Gson()
if(jsonElementFromServer.isJsonArray()){
//here you can just parse it into some list of array
}else{
//here you can parse using gson to single item element or model
}
}
JsonElement ref
Using Gson to get list of items or single model
As the author of the 2nd post you referred I also refer to the implementation of PostArrayOrSingleDeserializer described in that answer of mine.
When using Gson with Retrofit (Retrofit's converter-gson) you just need to register the adapter with custom Gson instance and build the Retrofit instance with that Gson instance, see below example helper class:
public class MyRetrofit {
public static MyAPI getMyApi() {
Gson gson = new GsonBuilder()
.registerTypeAdapter(Object.class,
new ObjectArrayOrSingleDeserializer())
.create();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://example.org")
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
return retrofit.create(MyAPI.class);
}
}
So the Object in the JsonDeserializer named ObjectArrayOrSingleDeserializer is the DTO you need to check for single instance or array. Replace Object with corresponding DTO and modify deserializer accordingly.

How to parse dynamic JSON with Retrofit?

I have dynamic JSON, here is example: http://pastebin.com/QMWRZTrD
How I can parse it with Retrofit?
I failed to generate POJO classes, since I have dynamic fields like "5411" and "5412".
EDIT:
I solved it by using Map, since first value is always integer, and second is list of objects.
#FormUrlEncoded
#POST("history.php")
Observable<Map<Integer, List<Vehicle>>> getHistory(#Field("uredjaji") String vehicleId, #Field("startDate") String startDATE, #Field("endDate")
you can use Map to serialize and deserialize it in case of Random keys.
Observable<Map<Integer, List<YourObject>>>
You can get retrofit api call to return String in your RestApi Interface like
Call<String> method(#Path(..)...);
And for that to work you would need to add the scalars converter factory to where you create your Retrofit object.
First you would need to import it:
compile 'com.squareup.retrofit2:converter-scalars:2.1.0'
And then add it:
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("https://your.base.url/")
.build();
And then in onResponse
public void onResponse(Call<String> call, Response<String> response) {
if (response.isSuccessful()) {
Type mapType = new TypeToken<Map<String,List<SomeClass>>() {}.getType(); // define generic type
Map<String,List<SomeClass>> result= gson.fromJson(response.body(), mapType);
} else {
}
}
Also,check out this site it has great tutorials on Retrofit.

How to preparse JSON results in Retrofit2 (before sending to GsonConverterFactory)

I am trying to consume a JSON using retrofit2 and GSON.
The following is the response provided by the server. Note that the value of "d" is a string of a valid JSON (once the slashes are removed).
{"d": "[{\"Number\":\"2121\",\"NumberOfAppearances\":2,\"Prizes\":
[{\"DrawDate\":\"\/Date(1439654400000)\/\",\"PrizeCode\":\"S\"},
{\"DrawDate\":\"\/Date(874771200000)\/\",\"PrizeCode\":\"S\"}]}]"}
Is there a way to use retrofit2 to preparse the the json during the call to retrofitService to get the objects inside the value of d?
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
//is there anything i can do here to do preparsing of the results?
.addConverterFactory(GsonConverterFactory.create())
.build();
IQueryLocations = retrofit.create(IMyQuery.class);
//currently GsonResults is the string value of d, instead of the JSON objects
Call<GsonResult> result = IMyQuery.doQuery("2121");
Ideally, I like to insert a method call before addConverterFactory to do the preparsing
the output of the preparsing method would be some thing like the following:
{"d": [{"Number":"2121","NumberOfAppearances":2,"Prizes":
[{"DrawDate": 1439654400000,"PrizeCode":"S"},
{"DrawDate": 874771200000,"PrizeCode":"S"}]}]}
It's not your ideal solution, but you can return a wrapper for the result data:
class WrappedGsonResult {
private static final Gson GSON = new Gson();
#SerializedName("d")
private String data;
GsonResult() {}
public GsonResult getData() {
return GSON.fromJson(this.data, GsonResult.class);
}
}
Then:
Call<WrappedGsonResult> result = IMyQuery.doQuery("2121");
result.enqueue(new Callback() {
#Override
public void onResponse(final Call<WrappedGsonResult> call, final Response<WrappedGsonResult> response) {
if (response.isSuccessful()) {
GsonResult result = response.body().getData();
// ...
} else {
// ...
}
}
// ...
});
To exclude double quotes, you need to use excludeFieldsWithoutExposeAnnotation() provided by GsonBuilder.
For example:
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
// Add Gson object
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
Hope this helps.

deserialize json with gson

I'm trying to deserialize json from worldbank.com to a pojo without any success. The json looks like:
[{"page":1,"pages":7,"per_page":"50","total":304},[{"id":"ABW","iso2Code":"AW","name":"Aruba","region":{"id":"LCN","value":"Latin America & Caribbean "},
and can be found via: http://api.worldbank.org/countries/?format=json
and im running into problems with gson telling me:
WorldBankDemo: com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 52 path $[1]
Any clues as to how i can solve this? Preferably without changing from gson since that is the lib used by the networking lib I'm using (retrofit)
WorldBankDataService service = ServiceFactory.createRetrofitService(WorldBankDataService.class, WorldBankDataService.SERVICE_ENDPOINT);
service.getCountries()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<WorldBank[]>() {
#Override
public final void onCompleted() {
// do nothing
}
#Override
public final void onError(Throwable e) {
Log.e("WorldBankDemo", e.getMessage());
}
#Override
public final void onNext(WorldBank[] response) {
Log.d("TAG", "resp: "+response);
//mCardAdapter.addData(response);
}
});
public class ServiceFactory {
/**
* Creates a retrofit service from an arbitrary class (clazz)
* #param clazz Java interface of the retrofit service
* #param endPoint REST endpoint url
* #return retrofit service with defined endpoint
*/
public static <T> T createRetrofitService(final Class<T> clazz, final
String endPoint) {
final RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(endPoint)
.setLogLevel(RestAdapter.LogLevel.FULL)
.build();
T service = restAdapter.create(clazz);
return service;
}
}
public class WorldBank {
int page;
int pages;
String per_page;
int total;
//Country[] countrys;
}
JSON is not constructed well(especially for auto parsing), Array can contain objects or arrays but not both at same level, in the above JSON structure it starts with Array in that the first element is an object and second element is an array, so this kind of JSON structure is not recommended for auto parsing, if at all you want to continue with same JSON response you can go for manual parsing or change response structure.
It's actually a JSON array. so you can't use class. try this:
YourPojo[] objects = gson.fromJson(jsonString, YourPojo[].class)
works like a charm
try this way
Gson gson = new Gson();
String jsonOutput = "Your JSON String";
Type listType = new TypeToken<List<ApiResponse>>(){}.getType();
List<ApiResponse> posts = (List<ApiResponse>) gson.fromJson(jsonOutput, listType);
and ApiResponse is like
public class ApiResponse{
WorldBank object1;
ArrayList<Country> objects2;
}
I haven't try this on my end, but it will be similar like that.
You can use gson to customize using this dependency
compile 'org.immutables:gson:2.3.1'
But slightly different way while invoking the rest client
For instance .If we have to get a list of countries declare an interface
public interface GetAllAPI {
#GET("/all")
List<Country> getCountries();
}
Now rest client will be
public List<Country> GetAllCountries() {
Gson gson = new GsonBuilder().create();
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(service_url)
.setConverter(new GsonConverter(gson))
.build();
GetAllAPI service = restAdapter.create(GetAllAPI.class);
List<Country> countrylist = service.getCountries();
return countrylist;
}
Getting the results from API will be
List<Country> countrylist = service.getCountries();
You have to customize this implementation for specific requirement. This is an idea how to implement Gson with Retrofit
Go through this for more clarification
Decided to give up and use another api, the world bank api just sucks :(

Retrofit Date Converter

I have a WCF Service that returns a List of objects to an Android app. One of the properties is a DateTime property. The return format is JSON and I am getting the date in this format /Date(1441117247253+0200)/ on the Android side. I am using com.squareup.retrofit:retrofit:1.9.0 to get the data from my service.
I have no clue how to use Retrofit to create a the string date into a Date object. I had a look at this http://square.github.io/retrofit/ under the Custom Converters section, but do not know how to go further. This is what I've tried but I do not know how to implement the converter class.
creating the restAdapter like so:
restAdapter = new RestAdapter.Builder()
.setConverter(new DotNetDateConverter())
.setLogLevel(RestAdapter.LogLevel.FULL)
.setEndpoint(API).build();
and the DotNetDateConverter Class which I do not know how to implement further:
public class DotNetDateConverter implements Converter
{
#Override
public Object fromBody(TypedInput body, Type type) throws ConversionException
{
return null;
}
#Override
public TypedOutput toBody(Object object)
{
return null;
}
}
there are other fields in the response which are fine, but how do I change the /Date(1441117247253+0200)/ to a proper java.util.Date object? Without the converter I get com.google.gson.JsonSyntaxException:/Date(1441117247253+0200)/ obviously because the string cannot be converter to a date.
Any assistance would greatly be appreciated.
Ok, so after some digging around, I came across this. My approach was wrong. So I ended up creating a Converter like this:
public class DotNetDateConverter implements JsonDeserializer<Date>
{
#Override
public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
{
String s = json.getAsJsonPrimitive().getAsString();
long l = Long.parseLong(s.substring(s.indexOf("(")+1, s.indexOf("+")));
Date d = new Date(l);
return d;
}
}
I also had to register it like so:
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Date.class, new DotNetDateConverter());
restAdapter = new RestAdapter.Builder()
.setConverter(new GsonConverter(gsonBuilder.create()))
.setLogLevel(RestAdapter.LogLevel.FULL)
.setEndpoint(API).build();
I had to change the code a bit to accommodate my specific scenario, that being dates come in as /Date(1441117247253+0200)/ with the time zone. Maybe someone else might find some use for this...

Categories

Resources