What are good practice for handling json over a Rest Framework in Android. For instance, if I get a certain json result as follow (or any other, I'm just giving something more complex):
{"lifts":
[{
"id":26,
"time":"2012-11-21T12:00:00Z",
"capacity":4,
"price":10,
"from": {
"description":null,
"city": {
"name":"Montreal"
}
},
"to":{
"description":"24 rue de la ville",
"city":{
"name":"Sherbrooke"
}
},
"driver":{
"first_name": "Benjamin",
"image":"https://graph.facebook.com/693607843/picture?type=large"
}
}
]}
1) Should I handle the result manually and get each value to populate my ui... (Not really)
2) Should I create a POJO for each object (to handle the mapping, with JSONObject). In my example, I will have to create a lift object that handle all the parameters and even create more POJO, to use for instance image and probably locations. (so basically, I constantly need to check my api rest framework to see how my object are done on server side, I'm duplicating my models from server to the android client).
3) Is there any framework to handle the mapping (serialize and deserialization).
I'm currently using option number 2, but was wondering if there was something better out there. It's working for me so far, for receiving and sending.
I like to create a response object per api endpoint where i map the response of the call.
For the given example and using GSON, the response object would be something like the following
public class Test
{
static String jsonString =
"{\"lifts\":" +
" [{" +
" \"id\":26," +
" \"time\":\"2012-11-21T12:00:00Z\"," +
" \"capacity\":4," +
" \"price\":10," +
" \"from\": { " +
" \"description\":null," +
" \"city\": {" +
" \"name\":\"Montreal\"" +
" }" +
" }," +
" \"to\":{" +
" \"description\":\"24 rue de la ville\"," +
" \"city\":{" +
" \"name\":\"Sherbrooke\"" +
" }" +
" }," +
" \"driver\":{" +
" \"first_name\": \"Benjamin\"," +
" \"image\":\"https://graph.facebook.com/693607843/picture? type=large\"" +
" }" +
" }" +
" ]}";
public static void main( String[] args )
{
Gson gson = new Gson();
Response response = gson.fromJson( jsonString, Response.class );
System.out.println( gson.toJson( response ) );
}
public class Response
{
#SerializedName("lifts")
List<Lift> lifts;
}
class Lift
{
#SerializedName("id")
int id;
#SerializedName("time")
String time;
#SerializedName("capacity")
int capacity;
#SerializedName("price")
float price;
#SerializedName("from")
Address from;
#SerializedName("to")
Address to;
#SerializedName("driver")
Driver driver;
}
class Address
{
#SerializedName("description")
String description;
#SerializedName("city")
City city;
}
class City
{
#SerializedName("name")
String name;
}
class Driver
{
#SerializedName("first_name")
String firstName;
#SerializedName("image")
String image;
}
}
Related
I have this json
https://maps.googleapis.com/maps/api/directions/json?origin=-22.8895625,-47.0714089&destination=-22.892376,-47.027553&key=
And I need deserialize it
But I get this error
Additional text encountered after finished reading JSON content: ,. Path '', line 8, position 4.
What I am doing:
public static async Task<List<Model.Localizacao>> GetDirectionsAsync(Localizacao locUser, Localizacao locLoja)
{
using (var client = new HttpClient())
{
try
{
List<Model.Localizacao> lstLoc = new List<Model.Localizacao>();
var json = await client.GetStringAsync("https://maps.googleapis.com/maps/api/directions/json?origin=" + locUser.latitude + "," + locUser.longitude + "&destination="+ locLoja.latitude+","+locLoja.longitude+"&key=" + GOOGLEMAPSKEY);
json = json.Substring(json.IndexOf('['));
json = json.Substring(0, json.LastIndexOf(']') + 1);
lstLoc = JsonConvert.DeserializeObject<List<Model.Localizacao>>(json);
return lstLoc;
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
return null;
}
}
}
this is my class:
namespace neoFly_Montana.Model
{
class Localizacao
{
public double latitude { get; set; }
public double longitude { get; set; }
}
}
How can I solve that?
My key is the same for the maps of google
I believe the problem is in these lines:
json = json.Substring(json.IndexOf('['));
json = json.Substring(0, json.LastIndexOf(']') + 1);
This appears to set json to be all the text from the first [ to the last ]. That means that you're going to wind up with some malformed json.
geocoded_waypoints is an array, but so is routes, which means you're going to wind up with a String that looks like this:
[
{ "geocoder_status" : "OK" ... }
{ "geocoder_status" : "OK" ... }
], "routes": [
{ "bounds": { ... } ... }
]
That , "routes": [ will fail to parse.
Update
After some discussion in the comments, I think I'm at the end of the help I'm able to provide. I'm not familiar with C# or the particular JSON parsing library you're using.
However, I can offer some ideas as a starting point.
The JSON coming back from that Google call has a particular structure. I suspect you will have to create new model classes that match this structure. For example, the top-level object would have three fields, and might look like this in Java:
public class ApiResponse {
private List<Waypoint> geocoded_waypoints;
private List<Route> routes;
private String status;
}
Then you'd have to implement Waypoint and Route, again matching the structure of the Google response:
public class Waypoint {
private String geocoder_status;
private String place_id;
private List<String> types;
}
public class Route {
private Bounds bounds;
private String copyrights;
private List<Leg> legs;
private Polyline overview_polyline;
private String summary;
private List<String> warnings;
private List<String> waypoint_order;
}
And so on. Once you have a class to represent the top-level response as well as all the various sub-objects inside that response, you would probably be able to change this code:
List<Model.Localizacao> lstLoc = new List<Model.Localizacao>();
var json = await client.GetStringAsync("https://maps.googleapis.com/maps/api/directions/json?origin=" + locUser.latitude + "," + locUser.longitude + "&destination="+ locLoja.latitude+","+locLoja.longitude+"&key=" + GOOGLEMAPSKEY);
json = json.Substring(json.IndexOf('['));
json = json.Substring(0, json.LastIndexOf(']') + 1);
lstLoc = JsonConvert.DeserializeObject<List<Model.Localizacao>>(json);
return lstLoc;
to this:
var json = await client.GetStringAsync("https://maps.googleapis.com/maps/api/directions/json?origin=" + locUser.latitude + "," + locUser.longitude + "&destination="+ locLoja.latitude+","+locLoja.longitude+"&key=" + GOOGLEMAPSKEY);
return JsonConvert.DeserializeObject<ApiResponse>(json);
I have a model that work with multiple JSON responses. However this a response :
items: [
{
kind: "youtube#playlistItem",
etag: ""fpJ9onbY0Rl_LqYLG6rOCJ9h9N8/jqbcTLu8tYm8b1FXGO14gNZrFG4"",
id: "PLUQ7I1jJqKB4lJarGcpWsP62l7iC06IkE2LDE0BxLe18",
That conflict with this one (notice the same id field with different type. The above id is String, the other is a Class) :
items: [
{
kind: "youtube#searchResult",
etag: ""fpJ9onbY0Rl_LqYLG6rOCJ9h9N8/hDIU49vmD5aPhKUN5Yz9gtljG9A"",
id: {
kind: "youtube#playlist",
playlistId: "PLh6xqIvLQJSf3ynKVEc1axUb1dQwvGWfO"
},
I want to read the id field using a single model class.
This is my model Response class :
public class Response {
private ArrayList<Item> items = new ArrayList<Item>();
And this is my model Item class :
public class Item {
private Id id;
//nested class inside Item
public class Id
{
private String id;
private String kind;
private String playlistId;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getKind() {
return kind;
}
public void setKind(String kind) {
this.kind = kind;
}
public String getPlaylistId() {
return playlistId;
}
public void setPlaylistId(String playlistId) {
this.playlistId = playlistId;
}
}
Notice that the Id class is inside Item class.
This is how i use registerTypeAdapter :
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Response.class,
new JsonDeserializer<Item.Id>() {
#Override
public Item.Id deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
Item.Id result = new Item().new Id();
if(jsonElement.isJsonPrimitive() == false)
{
result.setKind(jsonElement.getAsJsonObject().get("kind").getAsString());
result.setPlaylistId(jsonElement.getAsJsonObject().get("playlistId").getAsString());
//return new Item.Id(jsonElement.getAsJsonObject().get("kind").getAsString(), jsonElement.getAsJsonObject().get("playlistId").getAsString());
return result;
}
else
{
result.setId(jsonElement.getAsString());
//return new Item.Id(jsonElement.getAsString());
return result;
}
}
});
Gson gson = gsonBuilder.create();
Response result = Response.success(
gson.fromJson(json, gsonClass),
HttpHeaderParser.parseCacheHeaders(response));
However the above code throws java.lang.NullPointerException in this line :
if(jsonElement.isJsonPrimitive() == false)
What should i do?
Is this the correct way to use registerTypeAdapter?
Thanks for your time
Your main problem seems to be that you're registering a type adapter for Response class but you're giving a JsonDeserializer that handles Item.Id classes. Either you deserialize a full Response or limit yourself to Item or even Item.Id.
Another problem with deserializing Item.Id (as written in your question) directly is that it's a non-static inner class, which requires an instance of the parent class to be instantiated (as you do with the new Item().new Id()). I think that you would have to deserialize the whole Item manually if you want to keep it as such, frankly I don't see any reason not to make Item.Id static as it would simplify the problem.
Here's my solution with the static version of Item.Id
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(Item.Id.class, new JsonDeserializer<Item.Id>() {
#Override
public Item.Id deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Item.Id id = new Item.Id();
if (!json.isJsonPrimitive()) {
JsonObject jsonObject = json.getAsJsonObject();
id.setKind(jsonObject.get("kind").getAsString());
id.setPlaylistId(jsonObject.get("playlistId").getAsString());
} else {
id.setId(json.getAsString());
}
return id;
}
});
And some test snippets that seemed to work for me:
Gson gson = builder.create();
Response response = gson.fromJson("{\n" +
" \"items\": [\n" +
" {\n" +
" \"kind\": \"youtube#playlistItem\",\n" +
" \"etag\": \"fpJ9onbY0Rl_LqYLG6rOCJ9h9N8/jqbcTLu8tYm8b1FXGO14gNZrFG4\",\n" +
" \"id\": \"PLUQ7I1jJqKB4lJarGcpWsP62l7iC06IkE2LDE0BxLe18\"\n" +
" }\n" +
" ]\n" +
"}", Response.class);
Response response2 = gson.fromJson("{\n" +
" \"items\": [\n" +
" {\n" +
" \"kind\": \"youtube#searchResult\",\n" +
" \"etag\": \"fpJ9onbY0Rl_LqYLG6rOCJ9h9N8/hDIU49vmD5aPhKUN5Yz9gtljG9A\",\n" +
" \"id\": {\n" +
" \"kind\": \"youtube#playlist\",\n" +
" \"playlistId\": \"PLh6xqIvLQJSf3ynKVEc1axUb1dQwvGWfO\"\n" +
" }\n" +
" }\n" +
" ]\n" +
"}", Response.class);
If you want to keep Item.Id non-static, I think you need to write a deserializer for Item instead. Also keep in mind that json elements can be absent but the parser should still be able to handle it.
I had just the same problem long time ago, but i was working then with Json.Net, However json concept is (almost) the same so I hope I will succeed help.
I guess it might not work, but why try to not register with the Item class? I mean there is no 'Item.Id' in so it's obious get a null. BUT this: gsonBuilder.registerTypeAdapter(Item.class, likely to fail, becaus you dont parse the Item class explicitly (However it might work, I just never tried it in Gson. But in Json.Net similar way would work proper.)
So, how could you fix that? I'm too lazy to write an paraser but you could look here and read the surt summary summary from here (Search for Using the registerTypeAdapter().
I hope I helped.
In android, with SearchNotes.java example and a very simple add from me:
Log.d("something", title + " "+ note.getUpdated() + " "+ note.getNotebookGuid());
Api returns only 0 or null! Did I missed something?
More details of the context of the code:
try{
mEvernoteSession.getClientFactory().createNoteStoreClient()
.findNotesMetadata(filter, offset, pageSize, spec, new OnClientCallback<NotesMetadataList>() {
#Override
public void onSuccess(NotesMetadataList data) {
Toast.makeText(getApplicationContext(), R.string.notes_searched, Toast.LENGTH_LONG).show();
removeDialog(DIALOG_PROGRESS);
for(NoteMetadata note : data.getNotes()) {
String title = note.getTitle();
notesNames.add(title);
Log.d("something", title + " "+ note.getUpdated() + " "+ note.getNotebookGuid());
}
When you call NoteStore#findNotesMetadata, you can specify what it returns with NotesMetadetaResultSpec.
http://dev.evernote.com/documentation/reference/NoteStore.html#Fn_NoteStore_findNotesMetadata
http://dev.evernote.com/documentation/reference/NoteStore.html#Struct_NotesMetadataResultSpec
Make sure you set includeUpdated and includeNotebookGuid to true.
I got this part inside my json object. But I couldn't get this as a java List. It give error. So I tried to take it as a String Array. But it was same as before. So what should I use to parse this data to use with my android application ?
cuisine: {
-cuisine_names: [
"All (36)"
"Malaysian/ Singaporean (1)"
"Asian (1)"
"Australian (2)"
"Chinese (1)"
"European (3)"
"Spanish (1)"
"Greek (2)"
"Steak House (1)"
"Indian (1)"
"International (7)"
"Thai (1)"
"Italian (8)"
"Modern Australian (7)"
]
-price_ranges: [
"Any Price"
"$0-15"
"$15-30"
"$30+"
]
-times: [
"Any Time"
"05:30PM"
"06:00PM"
"06:30PM"
"07:00PM"
"07:30PM"
"08:00PM"
"08:30PM"
"09:00PM"
"09:30PM"
"10:00PM"
"10:30PM"
"11:00PM"
"11:30PM"
]
}
Thanks in advance !
To fill a list with a JSONObject, you should use a function like this (where NewsBSR is a custom object with some basic fields):
private ArrayList<NewsBSR> getListObjectsNews(JSONObject objNews)
{
ArrayList<NewsBSR> listNews = new ArrayList<NewsBSR>();
try{
for (Iterator iterator = objNews.keys(); iterator.hasNext();)
{
String cle = String.valueOf(iterator.next());
Object objet = objNews.get(String.valueOf(cle));
Log.v("V", "News: "+cle+ " : "+objet.toString());
if (cle.equals("results"))
{
JSONArray array = objNews.getJSONArray(cle);
for(int i = 0; i < array.length() ; i++)
{
Object obj = array.get(i);
Iterator it = ((JSONObject) obj).keys();
NewsBSR news = new NewsBSR();
while (it.hasNext())
{
String k = String.valueOf(it.next());
String val = ((JSONObject) obj).getString(k);
Log.v("V", "Array content : "+k+ " : "+val);
if (k.equals("tt") && val.length() > 0)
{
news.setTitle(val);
}
if (k.equals("dt") && val.length() > 0)
{
news.setDate(UtilsDate.stringToDate(val));
}
if (k.equals("num") && val.length() > 0)
{
news.setId(val);
}
}
listNews.add(news);
}
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
Log.v("V", "Error HOME: "+ e.getMessage());
e.printStackTrace();
}
return (listNews);
}
I think you have forgotten put commas. And you can use this
http://www.androidcompetencycenter.com/2009/10/json-parsing-in-android/
The json format is not supposed to be like that.
first, there should be a root object surrounding the codes like this.
{ <---this
cuisine: {
-cuisine_names: [
"All (36)"........
} <-- and this
and then you need commas between every string in the array like so
"Australian (2)",
"Chinese (1)",
"European (3)",
"Spanish (1)",
"Greek (2)",
"Steak House (1)",
It's very simple with Gson:
public class Foo
{
static String jsonInput =
"{" +
"\"cuisine\": {" +
"\"cuisine_names\": [" +
"\"All (36)\"," +
"\"Malaysian/ Singaporean (1)\"," +
"\"Asian (1)\"," +
"\"Australian (2)\"," +
"\"Chinese (1)\"," +
"\"European (3)\"," +
"\"Spanish (1)\"," +
"\"Greek (2)\"," +
"\"Steak House (1)\"," +
"\"Indian (1)\"," +
"\"International (7)\"," +
"\"Thai (1)\"," +
"\"Italian (8)\"," +
"\"Modern Australian (7)\"" +
"]," +
"\"price_ranges\": [" +
"\"Any Price\"," +
"\"$0-15\"," +
"\"$15-30\"," +
"\"$30+\"" +
"]," +
"\"times\": [" +
"\"Any Time\"," +
"\"05:30PM\"," +
"\"06:00PM\"," +
"\"06:30PM\"," +
"\"07:00PM\"," +
"\"07:30PM\"," +
"\"08:00PM\"," +
"\"08:30PM\"," +
"\"09:00PM\"," +
"\"09:30PM\"," +
"\"10:00PM\"," +
"\"10:30PM\"," +
"\"11:00PM\"," +
"\"11:30PM\"" +
"]" +
"}" +
"}";
public static void main(String[] args)
{
GsonBuilder gsonBuilder = new GsonBuilder();
Gson gson = gsonBuilder.create();
CuisineContainer cc = gson.fromJson(jsonInput, CuisineContainer.class);
System.out.println(cc);
}
}
class CuisineContainer
{
private Cuisine cuisine;
#Override
public String toString()
{
return cuisine.toString();
}
}
class Cuisine
{
private String[] cuisine_names;
private String[] price_ranges;
private String[] times;
#Override
public String toString()
{
StringBuilder result = new StringBuilder();
result.append("cuisine_names: ");
result.append(Arrays.asList(cuisine_names));
result.append(System.getProperty("line.separator"));
result.append("price_ranges: ");
result.append(Arrays.asList(price_ranges));
result.append(System.getProperty("line.separator"));
result.append("times: ");
result.append(Arrays.asList(times));
return result.toString();
}
}
output:
cuisine_names: [All (36), Malaysian/ Singaporean (1), Asian (1), Australian (2), Chinese (1), European (3), Spanish (1), Greek (2), Steak House (1), Indian (1), International (7), Thai (1), Italian (8), Modern Australian (7)]
price_ranges: [Any Price, $0-15, $15-30, $30+]
times: [Any Time, 05:30PM, 06:00PM, 06:30PM, 07:00PM, 07:30PM, 08:00PM, 08:30PM, 09:00PM, 09:30PM, 10:00PM, 10:30PM, 11:00PM, 11:30PM]
I am having a problem when I create an array of strings, this only happens in 2.1 android api level 7 or lower and i need to install the application on a device with exactly this configuration, any idea how to solve the problem?
Below the source code, the message that pops up on screen and also logcat's message.
CODE:
private String[] fillPedidosName() {
TipoPedidoDAO tipoDAO = null;
try {
tipoDAO = new TipoPedidoDAO();
pedidosList = tipoDAO.selectAll();
String[] temp = new String[pedidosList.size()];
for (int i = 0; i < pedidosList.size(); i++) {
if (pedidosList.get(i) != null) {
temp[i] = pedidosList.get(i).getDescricao().toString();
}
}
return temp;
} catch (Exception ex) {
MyLoger.logar(ex);
return null;
} finally {
if (tipoDAO.getDB().isOpen()) {
tipoDAO.closeConnection();
}
}
}
THE MESSAGE THAT POPS UP DEBUGING:
Exception processing async thread queue
Exception processing async thread queue
java.lang.UnsupportedOperationException
lOGCAT'S MESSAGE:
03-03 17:57:57.124: ERROR/jdwp(1267): REQ: UNSUPPORTED (cmd=2/11 dataLen=8 id=0x0012ba)
You are probably not using a List that supports get(int).
Try changing your List implementation to ArrayList. When you create your list:
List myList = new ArrayList()
This is probably happening inside tipDAO.selectAll().
I had this problem. I got it fixed.
When working with Arrays of your Objects, make sure you have defined a constructor in the object file.
This piece of code was creating the error
List<Prediction> predictions = new ArrayList<Prediction>();
The fix.
Prediction class file was missing a constructor. After putting in a default constructor, the error is gone for me.
package com.thuptencho.torontotransitbus.models;
public class Prediction {
public String epochTime = "", seconds = "", minutes = "", isDeparture = "", affectedByLayover = "", branch = "",
dirTag = "", vehicle = "", block = "", tripTag = "";
//this constructor was missing..after coding this constructor. the error was gone.
public Prediction(){
super();
}
#Override
public String toString() {
return "epochTime:" + this.epochTime + " seconds:" + this.seconds + " minutes:" + this.minutes
+ " isDeparture:" + this.isDeparture + " affectedByLayover:" + this.affectedByLayover + " branch:"
+ this.branch + " dirTag:" + this.dirTag + " vehicle:" + this.vehicle + " block:" + this.block
+ " triptag:" + this.tripTag;
}
}