37

I thought I understood how to do this, but obviously not. I have my API from Flickr, which begins like so:

jsonFlickrApi({
   "photos":{
      "page":1,
      "pages":10,
      "perpage":100,
      "total":1000,
      "photo":[
         {
            "id":"12567883725",
            "owner":"74574845@N05",
            "secret":"a7431762dd",
            "server":"7458",
            "farm":8,
            "title":"",
            "ispublic":1,
            "isfriend":0,
            "isfamily":0,
            "url_l":"http:\/\/farm8.staticflickr.com\/7458\/12567883725_a7431762dd_b.jpg",
            "height_l":"683",
            "width_l":"1024"
         }

Now the information I need to get is from within the photo array, so what I have been trying to do is:

interface ArtService {

    @GET("/services/rest/?method=flickr.photos.getRecent&extras=url_l&owner_name&format=json")
    PhotosResponse getPhotos();

    public class PhotosResponse {
        Photos photos;
    }

    public class Photos {
        List<Arraz> photo;
    }

    public class Arraz {
        int id;
        String title;
        String owner;
        String url_l;
    }
}

Very clear that I seem to be missing the point, however I am unsure of how to get the information..

K20GH
  • 6,032
  • 20
  • 78
  • 118

5 Answers5

46

I would suggest using http://www.jsonschema2pojo.org. You can paste your JSON and it will generate the POJOs for you.

That should do the trick.

LaSombra
  • 789
  • 1
  • 7
  • 12
42

A quick look at Retrofit's docs says it uses Gson to convert JSON to Java classes. This means you need a class hierarchy in Java that matches the JSON. Yours ... doesn't.

The returned JSON is an object with a single field "photos" that holds an object;

{ "photos" : { ... } }

So, your top level class would be a Java class with a single field:

public class PhotosResponse {
    private Photos photos;

    // getter/setter
}

And that Photos type would be another class that matches the JSON for the object that field contains:

{ "page":1, "pages":10, ... }

So you'd have:

public class Photos {
    private int page;
    private int pages;
    private int perpage'
    private int total;
    private List<Photo> photo;

    // getters / setters
}

And then you'd create a Photo class to match the structure of the object in that inner array. Gson will then map the returned JSON appropriately.

Brian Roach
  • 76,169
  • 12
  • 136
  • 161
  • Ok, so I have edited my first post to reflect the changes. How would I go about calling each variable in another class? I am using the following as an example to base off: https://github.com/romannurik/muzei/tree/master/example-source-500px/src/main/java/com/example/muzei/examplesource500px – K20GH Feb 16 '14 at 18:31
  • @Brian Roach are these classes separate or can they be in on class file and if one file, what would be the name of the file... photosResponse or Photos? I am actually trying to use this format for an array. – Lion789 Mar 18 '14 at 10:21
  • 1
    Is there an easier way of twisting the JSON structure to map a class you already have? e.g. in this example, if all I care about is the photos key, I don't want to waste my time defining an object matching the structure if I don't plan to use it that way. I would rather define a mapping stating that I only want the photos property. – Ted Avery Jun 03 '14 at 04:04
  • 1
    Is this a good practice? I mean, I dont understand the point of using Retrofit to avoid parse JSON, but however I have to create as classes as nested objects/arrays are in the JSON. If you would parse the JSON manually you wouldn't have to create PhotosResponse class or Photos class, just create a Photo class and get the array with the Photos using gson to parse the Photo class. Are there any way to get the array of Photos without create so many classes using Retrofit? Thanks – FOMDeveloper May 27 '15 at 12:16
  • I think using retrofit is more than avoid JSON parsing on your own. Firstly, retrofit is way faster than volley or async task. With my personal experience, retrofit makes making service calls a pretty smooth process. Provides a very simplisitc callback mechanism. It lets you dynamically subsitute path-segment,POST,GET etc etc. And when you have applications that make more than 50 different calls, using retrofit the class looks really simply and clean making manipulation of the end points really easy and pain free. And you can use retrofit in conjucation with RxJava. I could keep rambling here. – user2511882 May 29 '15 at 18:06
  • 1
    @FOMDeveloper: Two extra classes is a small price to pay for following a standard way of deserializing JSON. Group your transport classes in a separate "transport"-package and you'll soon forget they exist. I touch upon the concept of transport classes in this blog post: http://www.nilzorblog.com/2015/04/dont-forget-view-model.html – Nilzor Jun 03 '15 at 14:03
11

The accepted answer is correct but it requires building a PhotoResponse class which only has one object in it. This the following solution, we only need to create the Photos class and some sterilization.

We create a JsonDeserializer:

class PhotosDeserializer implements JsonDeserializer<Photos>
{
    @Override
    public Photos deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {

        JsonElement content = json.getAsJsonObject().get("photos");

        return new Gson().fromJson(content, Photos.class);

    }

}

Now we create our custom gson object to Retrofit's RestAdapter:

    Gson gson = new GsonBuilder()
                    .registerTypeAdapter(Photos.class, new PhotosDeserializer())
                    .create();

And then we set the converter in the retrofit adapter:

 RestAdapter restAdapter = new RestAdapter.Builder()
                                            .setEndpoint(ArtService.ENDPOINT)
                                            .setConverter(new GsonConverter(gson))
                                            .build();

And the interface would look like this:

@GET("/?method="+METHOD_GET_RECENT+"&api_key="+API_KEY+"&format=json&nojsoncallback=1&extras="+EXTRAS_URL)
public void getPhotos(Callback<Photos> response);

This way we get the Photos object without having to create PhotosResponse class. We can use it like this:

ArtService artService = restAdapter.create(ArtService.class);
artService.getPhotos(new Callback<Photos>() {
    @Override
    public void success(Photos photos, Response response) {

        // Array of photos accessing by photos.photo

   }

   @Override
   public void failure(RetrofitError error) {


    }
});
Mika
  • 5,807
  • 6
  • 38
  • 83
FOMDeveloper
  • 4,370
  • 3
  • 21
  • 20
  • This is not generic at all. The point of using Retrofit, is to be able to request several api overone defined endpoint. What is the point of loosing the ability to parse answer independently ? – Gomino Jun 03 '15 at 15:13
  • I agree. I just wanted to know how good practice is create as classes as nested objects/arrays are in a Json. Probably this approach would fit better in a huge Json file where you just want an object from it, instead for a small Json. Anyway this answer gives a solution to the original question using retrofit without having to create the root class. @Nilzor make a good point with his comment. Maybe it worth create all these transport classes and group them in a package. – FOMDeveloper Jun 03 '15 at 15:54
  • Then what about my answer ? How does it not answer the OP question ? – Gomino Jun 03 '15 at 15:57
  • and how can we iterate through array of "photo" in success callback method of Retrofit? – tsiro Nov 12 '15 at 21:27
  • I am getting **cannot resolve symbol GsonConverter** – Narendra Singh Nov 01 '16 at 20:57
2

You should be able to directly access the com.google.gson.JsonObject from Retrofit, and access whatever field you would like. So if you are only interested in the Photo array something like this should works:

interface ArtService {
    @GET("/services/rest/?method=flickr.photos.getRecent&extras=url_l&owner_name&format=json")
    JsonObject getPhotos();

    public class Photo {
         int id;
         String title;
         String owner;
         String url_l;
    }
}

And when you actually call the service just run the JsonObject to get what you want:

    JsonObject json = mRestClient.getArtService().getPhotos();
    List<ArtService.Photo> photos = new Gson().fromJson(json.getAsJsonObject("photos").get("photo").toString(), new TypeToken<List<ArtService.Photo>>() {}.getType());

Of course all the sanity check are left to you.

Gomino
  • 12,127
  • 4
  • 40
  • 49
  • Thanks for your response. Can you please add an explanation on the second line. Is that a retrofit call? Where do the success and fail blocks go? – FOMDeveloper Jun 03 '15 at 12:00
  • Nope it's from Gson. I first search the JsonObject tree for the selected field then I manually convert the json string back to a list of your custom type using Gson TypeToken. What do you mean by success and fail block ? You are using Retrofit synchronously so simply try to catch a RetrofitError around your api call – Gomino Jun 03 '15 at 12:10
1

As already few answers are above which you can use. But as per my view use this. Make a photo class with all the variables given in photos object and create getter setter of all and also create a class of photo with will hold the list of photos and also create getter setter of this list in side the Photos class. Below is the code given.

public static class Photos {

        @JsonProperty("page")
        private double page;
        @JsonProperty("pages")
        private double pages;
        @JsonProperty("perpage")
        private double perpage;
        @JsonProperty("total")
        private double total;

        @JsonProperty("photo")
        private List<Photo> photo;


        public double getPage() {
            return page;
        }

        public void setPage(double page) {
            this.page = page;
        }

        public double getPages() {
            return pages;
        }

        public void setPages(double pages) {
            this.pages = pages;
        }

        public double getPerpage() {
            return perpage;
        }

        public void setPerpage(double perpage) {
            this.perpage = perpage;
        }

        public double getTotal() {
            return total;
        }

        public void setTotal(double total) {
            this.total = total;
        }
    }

    public static class Photo {
        // refer first class and declare all variable of photo array and generate getter setter.
    }
Vid
  • 1,012
  • 1
  • 14
  • 29