Object inside object with retrofit - android

i need to send #Body like next:
{
"test": "test",
"test2": {
"test2": "test2",
"test2": "test2",
},
"test3": {
"test3": "test3",
"test3": "test3",
},
}
I am new with retrofit, I know how to create simple #Body object, but how create objects inside object - I have no idea.
will be glad any help!

Just create classes for these inner objects, and aggregate them into one object:
class TestWrapper {
#Expose
String test;
#Expose
Test2 test2;
#Expose
Test3 test3;
}
class Test2 {
#SerializedName("something_name") // <- this will be the JSON key name
#Expose
String something;
#SerializedName("something_else_name")
#Expose
String somethingElse;
}
etc.
Then pass the TestWrapper object as the request #Body.
Also, not that in your JSON you named two objects the same ("test2", "test3") - you can't do it, keys must be unique.
Annotations in this code are the GSON library annotations:
#Expose and
#SerializedName

Related

How to parse nested json using retrofit in expandable Listview? [duplicate]

Our team decide to use Retrofit 2.0 and I'm doing some initial research on this library. As stated in the title, I want parse some nested JSON objects via Retrofit 2.0 in our Android app.
For example, here is a nested JSON object with the format:
{
"title": "Recent Uploads tagged android",
"link": "https://www.flickr.com/photos/tags/android/",
"description": "",
"modified": "2015-10-05T05:30:01Z",
"generator": "https://www.flickr.com/",
"items": [
{
"title": ...
"link": ...
"media": {"m":"This is the value I want to get:)"}
"description": ...
"published": ...
"author": ...
"author_id": ...
"tags": ...
},
{...},
...
]
}
I'm interested in the JSON objects inside items array. I notice there are some posts about parsing nested JSON objects via Retrofit 1.X, but the latest Retrofit 2.0 APIs has changed a lot, which is confusing when adapting them to the new APIs.
Two possible solutions come into my mind:
Write my own JSON converter factory which extends Converter.Factory.
Return the raw response in a String type and parse it by myself. But it's not easy to get the raw response from Retrofit 2.0 according to my initial research. Retrofit 2.0 seems to insist in converting the response to something before pass it to me and Retrofit doesn't provide its own StringConverter. (I might be wrong~)
Update: We can actually get the raw response by setting JSONElement as the pojo for the HTTP API interface and use GSONConverter provided by Retrofit as the converter.
Assuming your complete JSON looks like
{
"title": "Recent Uploads tagged android",
"link": "https://www.flickr.com/photos/tags/android/",
"description": "",
"modified": "2015-10-05T05:30:01Z",
"generator": "https://www.flickr.com/",
"items": [
{
"member1": "memeber value",
"member2": "member value"
},
{
"member1": "memeber value",
"member2": "member value"
}
]
}
So Pojo classes would be
public class MainPojo {
private String title;
private String description;
private String link;
private String generator;
private String modified;
private ArrayList<Items> items;
// Getters setters
}
public class Items {
private String member2;
private String member1;
// Getters setters
}
Note : This is similar solution for your JSON. Members of Items.java can be changed if JSON has other keys.
Update for Pojo as new JSON
public class Items {
private String tags;
private String author;
private String title;
private String description;
private String link;
private String author_id;
private String published;
private Media media;
// Getters and Setters
}
public class Media {
private String m;
// Getters and Setters
}
Following code will help to get nested json object and array
for example: json
{
"similar_product":[
{ .....
}
],
"options":{
"Blouse Length":[
{ "value_id":"696556",
}
first we need to create model class, model class items names are same in json item we can use #SerializedName("for exact json name")
public class Product {
public Options options;
public void setOptions(Options options) {
this.options = options;
}
public Options getOptions() {
return options;
}
// length...
public class Options
{
#SerializedName("Blouse Length")
private ArrayList<BlouseLength> blouseLengths;
public void setBlouseLengths(ArrayList<BlouseLength> blouseLengths) {
this.blouseLengths = blouseLengths;
}
public ArrayList<BlouseLength> getBlouseLengths() {
return blouseLengths;
}
}
public class BlouseLength {
String value_id;
public void setValue_id(String value_id) {
this.value_id = value_id;
}
public String getValue_id() {
return value_id;
}
}
}
create Interface for retrofit to get json item in url
// don't need to put values of id in retrofit
ex:: "/api-mobile_.php?method=getProductById&pid="
just pass url parameter in query it automatically fetch the url
for example:
public interface Retrofit_Api {
#FormUrlEncoded
#GET("/api-mobile_.php?method=getProductById")
Call<Product> responseproduct(#Query("pid") String pid);
}
In your Main class
String pid=editid.getText().toString();
final Retrofit adapter = new Retrofit.Builder()
.baseUrl(Product_url)
.addConverterFactory(GsonConverterFactory.create())
.build();
//Creating an object of our api interface
Retrofit_Api api = adapter.create(Retrofit_Api.class);
Call<Product> call = api.responseproduct(pid);
call.enqueue(new Callback<Product>() {
#Override
public void onResponse(Call<Product> call, Response<Product> response) {
ArrayList<Product.BlouseLength> p= new ArrayList(response.body().getOptions().getBlouseLengths());
Editadapter editadapter=new Editadapter(MainActivity.this,p);
recyclerView.setAdapter(editadapter);
}
#Override
public void onFailure(Call<Product> call, Throwable t) {
Log.d("Error", t.getMessage());
}
});
}
Use Gson easy parsing for your models https://github.com/google/gson
My Helper Methods :
public String toJson(Object object) {
return gson.toJson(object);
}
public <T> T fromJson(String json, Class<T> classOfT) {
return gson.fromJson(json, classOfT);
}
public <T> T fromJson(JsonElement jsonElement, Class<T> classOfT) {
return gson.fromJson(jsonElement, classOfT);
}
have you tried volley? ...i prefer it over retrofit now that is a google product.I have working example and if u dont mind i can show you.
http://www.androidhive.info/2014/09/android-json-parsing-using-volley/
I forgot add #SerializedName and #Expose annotations for inner Class objects and after add these annotations problem solved. Like this:
JSON:
{"Id": 1,}
and Class member:
#SerializedName("Id")
#Expose
private int id;

Android Retrofit and GSON: JSON response returns object or string as property value

The JSON data returned from the server can either return an object, or if that object is null, it returns an empty string ("").
My problem is that my DTO expects an object, but it sees a string and crashes.
PersonDTO
data class PersonDto(
#SerializedName("firstName") val first: String,
#SerializedName("lastName") val last: String,
#SerializedName("favorites") val favorites: FavoriteDto,
)
FavoriteDto
class FavoriteDto(
#SerializedName("color") val color: String,
#SerializedName("number") val number: Int
)
Different responses from server
"person" : {
"firstName": "Steve",
"lastName" : "Johnson",
"favorites" : {
"color": "Purple",
"number": 25
}
}
...
"person" : {
"firstName": "Steve",
"lastName" : "Johnson",
"favorites" : ""
}
I've heard that I might need a custom GSON deserializer, but I've never done anything with GSON other than the out of the box stuff - so I was hoping for a nudge in the right direction.
Thanks!
Easiest hack is that you can add extra fields in the class with the same serialised name but with a String data type. Like this -
data class PersonDto(
#SerializedName("firstName") val first: String,
#SerializedName("lastName") val last: String,
#SerializedName("favorites") val favorites: FavoriteDto,
#SerializedName("favorites") val favoritesStr: String,
)
As there is nothing in Gson as "Required" field, you'll just get a null in your deserialized object if something is missing in the JSON. So, if there is an empty string the FavoriteDto object will be null and not null otherwise.
EDIT
I'm adding some Java code that I have written earlier. This might help:
public class PersonDto {
private FavoriteDto favorites;
private String favoritesStr;
public FavoriteDto getResponseDataObject() {
return favorites;
}
public void setResponseDataObject(FavoriteDto favorites) {
this.favorites = favorites;
}
public String getResponseDataString() {
return favoritesStr;
}
public void setResponseDataString(String favoritesStr) {
this.favoritesStr = favoritesStr;
}
Defining the Deserializar:
public static class ArrayObjectDualityDeserializer implements JsonDeserializer<PersonDto> {
public PersonDto deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {
PersonDto response = new PersonDto();
JsonObject object = json.getAsJsonObject();
if(object.get("favorites").isJsonArray()) {
} else if(object.get("favorites").isJsonObject()) {
try {
FavoriteDto dtoObject = gson.fromJson(object.get("favorites"), FavoriteDto.class);
response.setResponseDataObject(dtoObject);
} catch (JsonSyntaxException e) {
DebugLogger.e("Error " + e);
}
} else if (object.get("favorites").isJsonNull()) {
} else {
response.setResponseDataString(object.get("favorites").getAsString());
}
}
}
And:
public static Gson gson = new GsonBuilder()
.registerTypeAdapter(PersonDto.class, new ArrayObjectDualityDeserializer())
.create();
Lastly:
private static Retrofit retrofit = null;
private static OkHttpClient.Builder httpClient = null;
private static OkHttpClient session_client = null;
httpClient = new OkHttpClient.Builder();
httpClient.addInterceptor(new SessionOkHttpInterceptor());
session_client = httpClient.build();
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(session_client)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
The answer of #Subrato M. is partially correct. In order to work, you should remove #SerializedName("favorites") annotation from both fields in order to work. In this case, Gson won't throw an error when deserialize multiple json fields named favorites because you don't have any field annotated with that name (also don't name fileds the same as expected field name beucase Gson will try to deserialize). Use the custom deserializer to check if is an object or a string.
Nowadays isn't necessary the #Subrato M's hack, you don't get the right json cause you are missing the data keyword before class FavoriteDto and that is why doesn't exists the get and set methods

Retrofit 2: Handling dynamic JSON response

I am using retrofit 2 as a REST client and I cannot figure out how to deserialise a dynamic JSON response.
Depending on the status value (success or failure), our JSON can have two different objects in the result field:
A successful response returns a User object:
{
"status": 200,
"message": "OK",
"result": {
"id": "1",
"email": "bla#bla.bla"
...
}
}
A failed response returns an Error object:
{
"status": 100,
"message": "FAILED",
"result": {
"error": "a user with this account email address already exists"
}
}
I have created 3 POJO classes...
APIResponse:
public class APIResponse<T> {
#Expose private int status;
#Expose private String message;
#Expose private T result;
...
}
User:
public class User {
#Expose private String id;
#Expose private String email;
...
}
Error:
public class Error {
#Expose private String error;
...
}
Here is how I make the call:
#FormUrlEncoded
#PUT(LOGIN)
Call<APIResponse<User>> login(#Field("email") String email, #Field("password") String password);
And here is how I get a response:
#Override
public void onResponse(Call<APIResponse<User>> call, Response<APIResponse<User>> response) {
...
}
#Override
public void onFailure(Call<APIResponse<User>> call, Throwable t) {
...
}
Question:
The API call expects a return type of Call<APIReponse<User>>. However, it might not get a User object back... So how do I modify this approach to accept either APIResponse<User> or APIResponse<Error>.
In other words, how do I deserialise JSON data that can be in two different formats?
Solutions I have looked at:
Including 'error' field in User class or extending Error class (ugly).
Custom interceptor or converter (struggled to understand).
Convince API devs to change it and make my life easier :)
I am not sure if the solution is viable, but you can try changing <APIResponse<User>> to <APIResponse<Object>>
Call<APIResponse<Object>> login(#Field("email") String email, #Field("password") String password);
#Override
public void onResponse(Call<APIResponse<Object>> call, Response<APIResponse<Object>> response) {
//depending on the response status, you can cast the object to appropriate class
Error e = (Error)response.body().getResult();
//or
User u = (User)response.body().getResult();
}
or another alternative, use String instead of POJO
Call<String> login(#Field("email") String email, #Field("password") String password);
retrieve the JSON and serialize manually or use GSON
You should check the response code and use the desired class to process the json

JacksonConverter and ObjectMapper for nested objects

I am using Retrofit and JacksonConverter to fetch JSON data from an API.
The JSON response is like this:-
[
{
"id": 163,
"name": "Some name",
"nested": {
"id": 5,
"name": "Nested Name",
}
}
]
But in my class I only want the id, name of the parent object and the name of the nested object
I have this class :-
#JsonIgnoreProperties(ignoreUnknown = true)
class A{
#JsonProperty("id")
int mId;
#JsonProperty("name")
String mName;
#JsonProperty("nested/name")
String mNestedName;
}
I don't want to create another object for the nested object inside A. I just want to store the name field of the nested object in A.
The above class doesn't throw any exceptions but the mNestedName will be empty.
Is there anyway to do get the data like this? Do I need to change the #JsonProperty for mNestedName.
This is how I have declared my Retrofit instance
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(JacksonConverterFactory.create())
.baseUrl(mBaseUrl)
.client(okHttpClient)
.build();
return retrofit;
And I am getting the data through this:-
#GET("something/list/")
Call<List<A>> getA(#Header("Authorization") String authorization);
I don't have experience on Retrofit but if your problem is mostly related to Jackson. You can avoid generating POJO if you want by refactoring your A class with setter/getter and changing the setter param to Generic Object.
#JsonIgnoreProperties(ignoreUnknown = true)
public static class A{
#JsonProperty("id")
int mId;
#JsonProperty("name")
String mName;
#JsonIgnoreProperties
String mNestedName;
public String getmNestedName() {
return mNestedName;
}
#JsonProperty("nested")
public void setmNestedName(Object mNestedName) {
if(mNestedName instanceof Map) {
this.mNestedName = (String)((Map)mNestedName).get("name");
}
}

Retrofit android: gson array content deserialization

I'm working on API Requests with Retrofit(1.9.0) and gson library (1.7.0 I have compatibility issues with version 2.3.1 of gson) on Android, I make some request to an API which have same format of response but different content following the url call, but I encounter a problem for a deserialization of one answer which there is array inside. This is an example of the json I want to deserialize :
{
"http_code":200,
"content":[
{
"name":"Groult Christian",
"location":[
48.897655,
2.252462
],
"website":null,
"address":{
"street_address":"XXXXXX",
"city":"XXXXXX",
"state":null,
"postal_code":"XXXXXX",
"country":"XXXXXX"
},
"global_score":0,
"popularity_score":0,
"quality_score":0,
"createdAt":"2015-02-18T02:13:05.068Z",
"updatedAt":"2015-02-18T02:13:05.068Z",
"id":"54e3f531775288ca572872ac"
},
...
]
}
My DeserializerJson and how I call it for retrofit:
public class DeserializerJson<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("content");
// Deserialize it. You use a new instance of Gson to avoid infinite recursion
// to this deserializer
return new Gson().fromJson(content, type);
}
}
...
Gson gson = new GsonBuilder()
.registerTypeAdapter(ContentUser.class, new DeserializerJson<ContentUser>())
.registerTypeAdapter(DeviceInfo.class, new DeserializerJson<DeviceInfo>())
.registerTypeAdapter(PlacesModel.class, new DeserializerJson<PlacesModel>())
.create();
RestAdapter restAdapter = new RestAdapter.Builder()
.setLogLevel(RestAdapter.LogLevel.BASIC)
.setConverter(new GsonConverter(gson))
...
...
and my different models:
public class PlacesModel {
#SerializedName("name")
#Expose
private String name;
#SerializedName("location")
#Expose
private List<Double> location = new ArrayList<Double>();
#SerializedName("website")
#Expose
private Object website;
#SerializedName("address")
#Expose
private AddressModel address;
#SerializedName("global_score")
#Expose
private Integer globalScore;
#SerializedName("popularity_score")
#Expose
private Integer popularityScore;
#SerializedName("quality_score")
#Expose
private Integer qualityScore;
#SerializedName("createdAt")
#Expose
private String createdAt;
#SerializedName("updatedAt")
#Expose
private String updatedAt;
#Expose
#SerializedName("id")
private String id;
/* Getters and Setters... */
}
public class AddressModel {
#SerializedName("street_address")
#Expose
private String streetAddress;
#SerializedName("city")
#Expose
private String city;
#SerializedName("state")
#Expose
private Object state;
#SerializedName("postal_code")
private String postalCode;
#SerializedName("country")
#Expose
private String country;
/* Getters and Setters... */
}
url call in Api Manager is like this:
#GET("/places")
public void getPlaces(RestCallback<List<PlacesModel>> callback);
But when I do the call I get this error : com.google.gson.JsonParseException: The JsonDeserializer com.google.gson.DefaultTypeAdapters$CollectionTypeAdapter#3b8aa06 failed to deserialize json object
Everything is fine for other call I get all content and so with no problem but one where there is array inside content I got an error and I don't understand why I believed if I just put a list of my model it will be fine but it doesn't work.
I think I miss something so if someone can help me
Thanks in advance
The problem of your issue is that you register DeserializerJson for PlacesModel class and in getPlaces method your response is List<PlacesModel> class. List<PlacesModel> is a different class as PlacesModelso Gson doesn't know how to deserialise List<PlacesModel>. What you have to do is register one more Deserialiser by this method:
.registerTypeAdapter(List.class, new DeserializerJson<List<PlacesModel>>())
If you use more than one type of List (I mean List<PlacesModel> and List< DeviceInfo >) You can define your own TypeAdapter or you cN change list to array and register deserialiser for them as is shown below
.registerTypeAdapter(PlacesModel[].class, new DeserializerJson<PlacesModel[]>())
Now everything works fine for yours json.

Categories

Resources