Использование Retrofit для доступа к массивам JSON

Я думал, что понял, как это сделать, но, очевидно, нет. У меня есть API от Flickr, который начинается так:

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"
         }

Теперь информация, которую мне нужно получить, находится внутри массива фотографий, поэтому я пытался сделать следующее:

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;
    }
}

Совершенно ясно, что я, кажется, упускаю суть, однако я не уверен, как получить информацию.


person K20GH    schedule 16.02.2014    source источник
comment
Вы в конце разобрались?   -  person Lion789    schedule 18.03.2014


Ответы (5)


Беглый взгляд на документы Retrofit говорит, что он использует Gson для преобразования JSON в классы Java. Это означает, что вам нужна иерархия классов в Java, соответствующая JSON. Ваш... нет.

Возвращенный JSON представляет собой объект с одним полем «фотографии», которое содержит объект;

{ "photos" : { ... } }

Итак, ваш класс верхнего уровня будет классом Java с одним полем:

public class PhotosResponse {
    private Photos photos;

    // getter/setter
}

И этот тип Photos будет другим классом, который соответствует JSON для объекта, который содержит поле:

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

Итак, у вас будет:

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

    // getters / setters
}

Затем вы должны создать класс Photo, соответствующий структуре объекта в этом внутреннем массиве. Затем Gson соответствующим образом сопоставит возвращенный JSON.

person Brian Roach    schedule 16.02.2014
comment
Итак, я отредактировал свой первый пост, чтобы отразить изменения. Как мне вызвать каждую переменную в другом классе? Я использую следующий пример в качестве основы: github.com/romannurik/muzei/tree/master/example-source-500px/ - person K20GH; 16.02.2014
comment
@Brian Roach, эти классы отдельные или они могут быть в файле класса, и если это один файл, то как будет называться файл ... photosResponse или Photos? Я действительно пытаюсь использовать этот формат для массива. - person Lion789; 18.03.2014
comment
Есть ли более простой способ изменить структуру JSON, чтобы отобразить класс, который у вас уже есть? например в этом примере, если все, что мне нужно, это ключ фотографий, я не хочу тратить свое время на определение объекта, соответствующего структуре, если я не планирую использовать его таким образом. Я бы предпочел определить сопоставление, заявив, что мне нужно только свойство photos. - person Ted Avery; 03.06.2014
comment
Это хорошая практика? Я имею в виду, что я не понимаю смысла использования Retrofit, чтобы избежать синтаксического анализа JSON, но, тем не менее, мне нужно создавать классы, поскольку вложенные объекты/массивы находятся в JSON. Если бы вы анализировали JSON вручную, вам не нужно было бы создавать класс PhotosResponse или класс Photos, просто создайте класс Photo и получите массив с фотографиями, используя gson для анализа класса Photo. Есть ли способ получить массив фотографий без создания такого количества классов с помощью Retrofit? Спасибо - person FOMDeveloper; 27.05.2015
comment
Я думаю, что использование модификации — это больше, чем просто отказ от разбора JSON самостоятельно. Во-первых, модернизация намного быстрее, чем залповая или асинхронная задача. По моему личному опыту, модернизация делает обращение в службу поддержки довольно гладким процессом. Предоставляет очень простой механизм обратного вызова. Это позволяет вам динамически подставлять сегмент пути, POST, GET и т. д. И когда у вас есть приложения, которые делают более 50 различных вызовов, использование модификации класса выглядит очень просто и чисто, что делает манипуляции с конечными точками очень простыми и безболезненными. И вы можете использовать модификацию в сочетании с RxJava. Я мог бы продолжать болтать здесь. - person user2511882; 29.05.2015
comment
@FOMDeveloper: два дополнительных класса — это небольшая цена за использование стандартного способа десериализации JSON. Сгруппируйте свои транспортные классы в отдельный транспортный пакет, и вы скоро забудете об их существовании. Я коснусь концепции транспортных классов в этом сообщении блога: nilzorblog .com/2015/04/dont-forget-view-model.html - person Nilzor; 03.06.2015

Я бы предложил использовать http://www.jsonschema2pojo.org. Вы можете вставить свой JSON, и он сгенерирует для вас POJO.

Это должно делать свое дело.

person LaSombra    schedule 22.04.2014
comment
Прохладный. Я использовал вывод GSON в качестве руководства для своих интерфейсов в Retrofit. - person Ryan R; 22.10.2014
comment
Потрясающий! Он делает банки POJO! Супер! - person berserk; 04.12.2014

Принятый ответ правильный, но он требует создания класса PhotoResponse, в котором есть только один объект. Это следующее решение, нам нужно только создать класс Photos и некоторую стерилизацию.

Создаем 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);

    }

}

Теперь мы создаем наш пользовательский объект gson для RestAdapter Retrofit:

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

А потом устанавливаем преобразователь в дооснащенный адаптер:

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

И интерфейс будет выглядеть так:

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

Таким образом мы получаем объект Photos без необходимости создавать класс PhotosResponse. Мы можем использовать это так:

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) {


    }
});
person FOMDeveloper    schedule 03.06.2015
comment
Это вообще не универсально. Смысл использования Retrofit заключается в том, чтобы иметь возможность запрашивать несколько API через одну определенную конечную точку. Какой смысл терять возможность самостоятельно анализировать ответ? - person Gomino; 03.06.2015
comment
Я согласен. Я просто хотел знать, как хорошо создавать классы, поскольку вложенные объекты/массивы находятся в Json. Вероятно, этот подход лучше подходит для огромного файла Json, где вам просто нужен объект из него, а не для маленького Json. В любом случае этот ответ дает решение исходного вопроса с использованием модернизации без необходимости создания корневого класса. @Nilzor хорошо замечает своим комментарием. Может стоит создать все эти транспортные классы и сгруппировать их в пакет. - person FOMDeveloper; 03.06.2015
comment
Тогда как насчет моего ответа? Как это не отвечает на вопрос ОП? - person Gomino; 03.06.2015
comment
и как мы можем перебирать массив фотографий в успешном методе обратного вызова Retrofit? - person tsiro; 13.11.2015
comment
Я получаю сообщение не удается разрешить символ GsonConverter - person Narendra Singh; 01.11.2016

У вас должна быть возможность прямого доступа к com.google.gson.JsonObject из Retrofit и доступ к любому полю, которое вы хотите. Итак, если вас интересует только массив фотографий, должно работать что-то вроде этого:

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;
    }
}

И когда вы на самом деле вызываете службу, просто запустите JsonObject, чтобы получить то, что вы хотите:

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

Конечно, все проверки здравомыслия остаются на вас.

person Gomino    schedule 03.06.2015
comment
Спасибо за ваш ответ. Не могли бы вы добавить пояснение ко второй строке. Это призыв к переоснащению? Куда идут блоки успеха и неудачи? - person FOMDeveloper; 03.06.2015
comment
Нет, это от Gson. Сначала я ищу выбранное поле в дереве JsonObject, а затем вручную преобразовываю строку json обратно в список вашего пользовательского типа, используя Gson TypeToken. Что вы подразумеваете под блоком успеха и отказа? Вы используете Retrofit синхронно, поэтому просто попробуйте поймать RetrofitError вокруг вашего вызова API. - person Gomino; 03.06.2015

Поскольку уже есть несколько ответов, которые вы можете использовать. Но, на мой взгляд, используйте это. Создайте класс фотографий со всеми переменными, указанными в объекте фотографий, и создайте метод получения всех, а также создайте класс фотографий, в котором будет храниться список фотографий, а также создайте метод получения этого списка в стороне класса Photos. Ниже приведен код.

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.
    }
person Vid    schedule 03.06.2015