Custom retrofit json deserializer - android

I've been using Retrofit library recently and I like it so far. However, I've come across API that returns response with the following structure:
"data":
{
"meta":
{
"code": 200
"data": "data"
},
"body": [
{
"id": "id",
"field1": "data",
"field2": "data"
},
{
"id": "id",
"field1": "data",
"field2": "data"
}]
}
Assuming I have following class:
public class Entity {
long id;
String field1;
String field2;
}
and following interface:
public interface EntityService {
#GET("/api/method")
void getEntities(Callback<List<Entity>> callback);
}
what is the correct way to use Retrofit if I want to get a list of Entity objects from data.body element from response? I know I could have passed Response object in the callback and manually parse it using org.json package or something else, but is there any better way?

Why not just make a class that represents the data like you've already half done? That's what I've done in multiple apps along with other developers:
public class Meta {
String code;
String data;
}
public class Data {
Meta meta;
List<Entity> body;
}
public interface EntityService {
#GET("/api/method")
void getEntities(Callback<Data> callback);
}

It seems that extra data in your result could be re-used. You should use a generic class like this one:
public class Data<E> {
Meta meta;
E body;
public static class Meta {
int code;
String data;
}
}
Then you could use
Data<List<Entity>>
or whatever you want:
public interface EntityService {
#GET("/api/method")
void getEntities(Callback<Data<List<Entity>>> callback);
}

Related

Parsing unsuccessfully JSON Objects with retrofit2

I am using retrofit2 in my code with gson.
i can parse almost all needed, but when parsing a specific JSON, I have a problem.
This is the JSON:
{
"data": [
{
"id": "001",
"direccion": "Cascada 3",
"provincia": "Burgos"
},
{
"id": "002",
"direccion": "Lago 21",
"provincia": "Zamora"
}
]
}
I know that what I am going to write has been commented a lot but, please, read until the end
When parsing it shows this error:
Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $
If I delete the name of the object “data” from JSON, then I can parse it perfectly, like this:
[
{
"id": "001",
"direccion": "Cascada 3",
"provincia": "Burgos"
},
{
"id": "002",
"direccion": "Lago 21",
"provincia": "Zamora"
}
]
The JSON continues to open with an object, but it can be parsed perfectly.
This leads me to think that I am not indicating in the code how to parse the object called "data", I think the problem is that.
Tiendas.java (I use #serializedName for a possible solution that I have read but it does not work)
import com.google.gson.annotations.SerializedName;
public class Tiendas {
#SerializedName("id")
private int id;
#SerializedName("provincia")
private String provincia;
#SerializedName("direccion")
private String direccion;
public int getId() {
return id;
}
public String getProvincia() {
return provincia;
}
public String getDireccion() {
return direccion;
}
}
The interface
#GET("tiendas/tiends.json")
Call<List<Tiendas>> getTiendas();
MainActivity.java
private void getGETsinParametros() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://www.MY_URL_BASE.com/")
.addConverterFactory(GsonConverterFactory.create())
.build();
JsonPlaceHolderApi jspAPI = retrofit.create(JsonPlaceHolderApi.class);
Call<List<Tiendas>> call = jspAPI.getTiendas();
call.enqueue(new Callback<List<Tiendas>>() {
#Override
public void onResponse(Call<List<Tiendas>> call, Response<List<Tiendas>> response) {
if (!response.isSuccessful()) {
Log.e(TAG, "Código de respuesta no exítoso:" + response.code());
} else {
Log.d(TAG,"response code:"+response.code());
List<Tiendas> listaObtenida = response.body();
for(Tiendas stores: listaObtenida){
String content = "";
content += "id" + stores.getId() + "\n";
content += "provincia" + stores.getProvincia() + "\n";
content += "direccion" + stores.getDireccion() + "\n";
jsonText.append(content);
}
}
}
#Override
public void onFailure(Call<List<Tiendas>> call, Throwable t) {
Log.e("TAG, "error:" + t.getMessage());
}
});
}
Can someone tell me how to fix the error with "data", or with what is necessary to make it work?
The JSONs are external, so I can't modify them.
Thanks and regards.
You need to create the wrapper for the Array object to get the list of tiendas:
public class Data{
private List<Tiendas> data;
public List<Tiendas> getData() {
return data;
}
}
And call it in your interface:
#GET("tiendas/tiends.json")
Call<Data> getTiendas();
do not forget to use the new object at onResponse:
List<Tiendas> listaObtenida = response.body().getData();
The error has nothing to do with Retrofit. For a JSON, a field is an object, [] is an array, {} is a POJO.
In your first JSON, you have a data object with type List(or Array). Therefore, you will need to create a POJO on top (Access Modifier is omitted for simplicity)
class DataClazz {
List<Tiendas> data;
}

GSON: Deserialize json response and return different set of objects

I am trying to parse a json response using a GSON custom deserializer where after some modifications, it will return different set of objects. Currently, the sample json response is:
{
"data": {
"data_1": [
{
"attr": "linear",
"item": [
{
"category": "type1"
},
{
"category": "type2"
}
]
}
],
"data_2": [
{
"attr": "relative",
"item": [
{
"category": "type3"
},
{
"category": "type4"
}
]
}
]
}
}
For this, the model class is created as DataResponse.java. This is the CustomDeserialzer.java:
public class CustomDeserializer implements JsonDeserializer {
#Override
public Data deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
// It will create list of data1 and data2 and will return instance of Data
}
}
Data.java and DataItem.java:
class Data {
private List<DataItem> data1;
private List<DataItem> data2;
}
class DataItem {
private String attr;
private String category;
}
This deserializer is registered with gson with retrofit as:
final Retrofit retrofit = new Retrofit.Builder()
.baseUrl(endPoint)
.addConverterFactory(GsonConverterFactory
.create(new GsonBuilder()
.registerTypeAdapter(DataResponse.class, new ItemDeserializer())
.create()))
.client(okHttpClient)
.build();
In this case, custom deserializer is called with whole json response and everything works fine. But it gets called for all the api call response. If the class type in gson builder is changed to Data.class, the deserializer is never called. How to achieve this functionality such that deserializer can return Data with proper values?
The interface will have the method as follows:
public interface ApiService {
#GET("/getData")
Call<Data> getData();
}

JSON object parsing using retrofit2.0

I'm trying to parse a JSON object using retrofit 2.0 following this guide, but it doesn't work. I think it's because of a difference in JSON format.
Here is a nested JSON object with the format:
{
"SearchService": {
"list_total_count": 531,
"RESULT": {
"CODE": "INFO-001",
"MESSAGE": "SUCCESS"
},
"row": [{
"ID": "1983",
"NAME": "SAN",
"NUM": "38",
}, {
"ID": "1984",
"NAME": "DU",
"NUM": "27",
}]
}
}
Here is class code using SerializedName:
RowList.java
public class RowList {
#SerializedName("row")
#Expose
private ArrayList<Row> rows= new ArrayList<>();
public ArrayList<Row> getRows() {
return rows;
}
public void setRows(ArrayList<Row> rows) {
this.rows= rows;
}
}
Row.java
public class Row{
#SerializedName("ID")
#Expose
private String id;
#SerializedName("NAME")
#Expose
private String name;
#SerializedName("NUM")
#Expose
private String num;
/*getter setter*/
}
Read that guide.
There are two approaches to create Model class. The first way is the manual approach, which requires you to learn how to use the Gson library. The second approach is you can also auto-generate the Java classes you need by capturing the JSON output and using jsonschema2pojo
Looks like you've attempted approach one, but haven't (yet?) tried reading over the Gson documentation.
Okay, you have a Row. That covers the objects within "row": [...], so you also need objects for the following:
"SearchService": {}
"RESULT": {}
I don't think the RowList class is necessary. List<Row> is fine.
For example,
class Result {
#SerializedName("CODE")
String code;
#SerializedName("MESSAGE")
String message;
}
class SearchService {
#SerializedName("list_total_count")
long count;
#SerializedName("RESULT")
Result result;
#SerializedName("row")
private ArrayList<Row> rows= new ArrayList<>();
}
(removed #Expose for conciseness)
Then, Retrofit would use Call<SearchService>

Retrofit parse JSON dynamic keys

I'm a newbie in Retrofit. How to parse the Json below using retrofit?
{
"data": {
"Aatrox": {
"id": 266,
"title": "a Espada Darkin",
"name": "Aatrox",
"key": "Aatrox"
},
"Thresh": {
"id": 412,
"title": "o Guardião das Correntes",
"name": "Thresh",
"key": "Thresh"
}
},
"type":"champion",
"version":"6.23.1"
}
You could make your model POJO contain a Map<String, Champion> to deserialize into, to deal with the dynamic keys.
Example:
public class ChampionData {
public Map<String, Champion> data;
public String type;
public String version;
}
public class Champion {
public int id;
public String title;
public String name;
public String key;
}
I'm not familiar with Retrofit besides that, but as someone in the comments said, the deserializing is done by Gson:
public ChampionData champions = new Gson().fromJson(json, ChampionData.class);
So to build on to the answer someone else posted, you can then do the following, assuming you've added the GsonConverterFactory:
public interface API {
#GET("path/to/endpoint")
Call<ChampionData> getChampionData();
}
Assuming Retrofit2, the first thing you need to do is call following when building your Retrofit instance.
addConverterFactory(GsonConverterFactory.create())
Then it's just a matter of writing a POJO (e.g. MyPojoClass) that maps to the json and then adding something like following to your Retrofit interface.
Call<MyPojoClass> makeRequest(<some params>);

Gson throws "BEGIN_OBJECT expected but BEGIN_ARRAY found" - how to use TypeToken

Trying out Gson for the first time instead of looping through the JSON objects for speed.
This is my input data set for parsing
{
"data": [
{
"access_token": "XXXXX",
"category": "Community",
"name": "Startup notes by Vrashabh",
"id": "XXXXX",
"perms": [
"ADMINISTER",
"EDIT_PROFILE",
"CREATE_CONTENT",
"MODERATE_CONTENT",
"CREATE_ADS",
"BASIC_ADMIN"
]
},
{
"access_token": "XXXX",
"category": "Community",
"name": "Clean Bangalore",
"id": "XXXXX",
"perms": [
"ADMINISTER",
"EDIT_PROFILE",
"CREATE_CONTENT",
"MODERATE_CONTENT",
"CREATE_ADS",
"BASIC_ADMIN"
]
},
{
"access_token": "XXXXX",
"category": "Internet/software",
"name": "Getmeetin",
"id": "XXXXX",
"perms": [
"ADMINISTER",
"EDIT_PROFILE",
"CREATE_CONTENT",
"MODERATE_CONTENT",
"CREATE_ADS",
"BASIC_ADMIN"
]
}
],
"paging": {
"cursors": {
"before": "MTU3MzE3MTA0MjkyMjY4MQ==",
"after": "MjcyMTIwMzE2Mjk3NzI5"
}
}
}
And this is my gson mapping class
public class AccountsResponse {
ArrayList<AcResponseData> data;
ArrayList<PagingData> paging;
public class AcResponseData {
public String access_token;
public String category;
public String name;
public String id;
public String[] perms;
}
public class PagingData{
public Cursors cursors;
}
public class Cursors{
public String before;
public String after;
}
}
Code for parsing the data
AccountsResponse responseAccounts = gsonResponse.fromJson(response.getRawResponse(), AccountsResponse.class);
I Know I am supposed to not expect magic in terms of data conversion, I found the other questions on SO that ask me to implement TypeToken but I couldn't get it working for this case. How do I use TypeToken to get this data into the ORM
I wouldn't mind not reading that paging data also actually, if that needs to be eliminated from the ORM
UPDATE
Changed the ORM as below but now I get
java.lang.StackOverflowError
The problem is in your AccountsResponse class. It should be an object PagingData not an ArrayList, because from the json response in your question, "paging" is a JSON object not a JSON array. So, you should declare paging as a PagingData object not as an ArrayList of PagingData objects. That should fix it.
public class AccountsResponse {
ArrayList<AcResponseData> data;
PagingData paging;
public class AcResponseData {
public String access_token;
public String category;
public String name;
public String id;
public String[] perms;
}
public class PagingData{
public Cursors cursors;
}
public class Cursors{
public String before;
public String after;
}
}
Let me know if this helps.

Categories

Resources