I have the following problem:
The answers to my API are always like this "style":
{
"type": "dbUserResponse",
"user": {
"apellido": "canadas",
"email": "rj#canada.es",
"nacimiento": "1995-01-01T00:00:00+01:00",
}
}
That is, field "dbXXXXResponse" and model. That is, field "Response" and model. In this case user. Another example for the model Alert:
{
"type": "dbAlertResponse",
"alert": {
"alertId": 0,
"fecha": "2004-10-19T10:23:54+02:00",
}
}
Oki... What I need? Create a type Adapter deserialize or more?
One generic, or I have to make one typedapter for each model.
I have done the following:
public class GenericDeserializer<T> implements JsonDeserializer<T>
{
#Override
public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
throws JsonParseException
{
// Get the "content" element from the parsed JSON
JsonElement content = je.getAsJsonObject().get("type");
if(content.toString().equals("\"dbUserResponse\"")){
content = je.getAsJsonObject().get("user");
JsonObject jsonObject = content.getAsJsonObject();
if(jsonObject.has("nacimiento")){
String date = jsonObject.get("nacimiento").getAsString();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
Date date2 = null;
try {
date2 = sdf.parse(date);
} catch (ParseException e) {
e.printStackTrace();
}
String newstring = new SimpleDateFormat("yyyy-MM-dd").format(date2);
jsonObject.addProperty("nacimiento", newstring);
content = jsonObject.getAsJsonObject();
}
}
}
if(content.toString().equals("\"dbAlertResponse\"")){
// other case..... etc...
}
else{
//content = '{"error":"mierror"}';
}
// Deserialize it. You use a new instance of Gson to avoid infinite recursion
// to this deserializer
return new Gson().fromJson(content, type);
}
}
Thus, I have managed to successfully obtain the sub JSON "user" and fill my User model in Android.
But the "nacimiento" (date) field is not formatted :-(
I have also created a Singleton Retrofit, I'm not sure if it is correct:
public class SingletonRetrofit {
public static final String BASE_URL = "xxxxxxxxxxxxxxxxxxx";
private static Retrofit retrofit = null;
public static Retrofit getClient() {
Gson gson = new GsonBuilder()
.registerTypeAdapter(User.class, new GenericDeserializer<User>())
.create();
if (retrofit == null) {
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
}
return retrofit;
}
}
My questions:
1º- How correctly format the date when the model is inside another Json Json like me?
2º- Will I have to create a typeadapter for each model? : -S
Thank you
Why don't you use POJO? Date in JSON is String so you must convert String to Date.
Related
i am getting response in Json array and json object in same key.Json Array is working fine but while parsing json Object i am getting an error
Caused by: java.lang.IllegalStateException: Expected BEGIN_ARRAY but was
BEGIN_OBJECT at line 1 column 204 path $.RenewalPlans.RenewalPlan
Json Object
{"Code":"000","Message":"Success","UserName":"abc","CustomerName":"abc And
Company","PaymentMessage":"You have 55 days left, you can make ADVANCE
payment","RenewalPlans":{"RenewalPlan":{"PlanId":"11111","PlanName":"plan
name","PlanAmount":"1000"}}}
Json Array
{"Code":"000","CustomerName":"abc","Message":"Success","PaymentMessage":"You have 2 days left, you can make ADVANCE payment","RenewalPlans":{"RenewalPlan":[{"PlanAmount":"100","PlanId":"1","PlanName":"Value Standard 25mbps/12mths 1TV (Renew) "},{"PlanAmount":"200","PlanId":"2","PlanName":"30Mbps/12mths SPECIAL OFFER 2077 (1TV) "},{"PlanAmount":"300","PlanId":"3","PlanName":"40Mbps/12mths SPECIAL OFFER 2077 (2TV) (Rs.19775)"},{"PlanAmount":"400","PlanId":"4","PlanName":"60Mbps/12mths SPECIAL OFFER 2077 (3TV) "}]},"Username":"abcd"}
`
String data;
Object json = null;
CustomerdetailsResponse customerDetailsResponse = new Gson().fromJson(new Gson().toJson(response), CustomerdetailsResponse.class);
data = new Gson().toJson(customerDetailsResponse.getRenewalPlans().getRenewalPlan());
try {
json = new JSONTokener(data).nextValue();
if (json instanceof JSONObject) {
CustomerDetailsResponseRenewalPlansRenewalPlan plans = new Gson().fromJson(new Gson().toJson(data), CustomerDetailsResponseRenewalPlansRenewalPlan.class);
renewalPlans.add(plans);
}
//you have an object
else if (json instanceof JSONArray) {
renewalPlans = new Gson().fromJson(data, new TypeToken<ArrayList<RenewalPlans>>() {
}.getType());
}
} catch (JSONException e) {
e.printStackTrace();
}
factory register with Gson
public Gson gson()
{
return Converters.registerAll(new GsonBuilder())
.registerTypeAdapterFactory(AppTypeAdapterFactory.create())
.setLenient()
.create();
}
GsonType Adapter factory
#GsonTypeAdapterFactory
public abstract class AppTypeAdapterFactory implements TypeAdapterFactory {
public static TypeAdapterFactory create(){
return new AutoValueGson_AppTypeAdapterFactory();
}
}
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
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.
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 :(
I'm working with a REST API that returns a JSON document that starts as follows and includes a "collection" of items with string IDs like "ABC". Note the "routes" field, which contains a series of fields called "ABC", "ABD", "ABE" etc, however routes is not represented as an array in the json, so all these
{
"status":true,
"page":1,
"per_page":500,
"total_count":1234,
"total_pages":8,
"total_on_page":500,
"routes":{
"ABC":[
{
"value":22313,
<......>
I'm using Retrofit and the problem is the routes field is not an array (despite the fact conceptually it certainly is) and Retrofit/Gson require me to create a model object for routes with field vars abc, abd, etc - this is not practical as the data changes. For various reasons changing the server API is hard, so I'm looking to work around this on the Android client.
I figure these are options:
Intercept the JSON document before it reaches Gson and tweak the document, possibly with a customised Gson parser, or by intercepting the HTTP response.
Bypass the JSON parsing, and acquire the JSON document from Retrofit (I've yet to figure out how to do this, or if it's possible)
Use some feature of Retrofit I'm unaware of to map field names to a collection.
I'd appreciate help, especially if there's a quick and easy way to resolve this.
It turns out that Retrofit's use of Gson by default makes it fairly easy to add a custom deserialiser to handle the portion of the JSON document that was the problem.
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(ApiDefinition.BASE_URL)
.setConverter(getGsonConverter())
.build();
public Converter getGsonConverter() {
Gson gson = new GsonBuilder()
.registerTypeAdapter(RouteList.class, new RouteTypeAdapter())
.create();
return new GsonConverter(gson);
}
public class RouteTypeAdapter implements JsonDeserializer<RouteList> {
#Override
public RouteList deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Gson gson = new Gson();
RouteList routeList = new RouteList();
JsonObject jsonObject = json.getAsJsonObject();
for (Map.Entry<String,JsonElement> elementJson : jsonObject.entrySet()){
RouteList wardsRoutes = gson.fromJson(elementJson.getValue().getAsJsonArray(), RouteList.class);
routeList.addAll(wardsRoutes);
}
return routeList;
}
}
After calling RestService, don't use Model Name as argument, you have to use Default Response class from retrofit library.
RestService Method
#FormUrlEncoded
#POST(GlobalVariables.LOGIN_URL)
void Login(#Field("email") String key, #Field("password") String value, Callback<Response> callback);
Calling method in Activity
getService().Login(email, password, new MyCallback<Response>(context, true, null)
{
#Override
public void failure(RetrofitError arg0)
{
// TODO Auto-generated method stub
UtilitySingleton.dismissDialog((BaseActivity<?>) context);
System.out.println(arg0.getResponse());
}
#Override
public void success(Response arg0, Response arg1)
{
String result = null;
StringBuilder sb = null;
InputStream is = null;
try
{
is = arg1.getBody().in();
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
sb = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null)
{
sb.append(line + "\n");
result = sb.toString();
System.out.println("Result :: " + result);
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
});