Как разбирать список объектов JSON, окруженных [], с помощью Retrofit и GSON?

Я создал простую конечную точку REST:

http://<server_address>:3000/sizes

Этот URL-адрес возвращает очень простой ответ, содержащий массив json следующим образом:

[
  { "id": 1, "name": "Small", "active": true },
  { "id": 2, "name": "Medium", "active": true },
  { "id": 3, "name": "Large", "active": true }
]

Теперь я пытаюсь использовать этот ответ, используя Retrofit 2 с GSON.

Я добавил модель:

@lombok.AllArgsConstructor
@lombok.EqualsAndHashCode
@lombok.ToString
public class Size {
    private int id;
    private String name;
    private boolean active;

    @SerializedName("created_at")
    private String createdAt;

    @SerializedName("updated_at")
    private String updatedAt;
}

И сервис:

public interface Service {
    @GET("sizes")
    Call<List<Size>> loadSizes();
}

Я создал экземпляр "Дооснащения":

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("http://<server_address>:3000")
    .addConverterFactory(GsonConverterFactory.create())
    .build();

И мое обслуживание:

Service service = retrofit.create(Service.class);

Теперь, пытаясь вызвать данные:

service.loadSizes().enqueue(new Callback<List<Size>>() {
    @Override
    public void onResponse(Call<List<Size>> call, Response<List<Size>> response) {
        for(Size size: response.body()) {
            System.out.println(size.toString());
        }
    }

    @Override
    public void onFailure(Call<List<Size>> call, Throwable t) {
        System.out.println(t.getMessage());
    }
});

В результате получается исключение:

java.lang.IllegalStateException: Ожидаемый BEGIN_OBJECT, но был STRING в строке 1 column 18 path $[0].name

Я предполагаю, что ошибка вызвана тем, что REST API возвращает ответ, который представляет собой массив или объект.

  • Правильно ли я?
  • Каков самый простой способ заставить этот код работать?

Служба REST не может быть изменена, поэтому ответ должен оставаться как есть.

Кроме того, десериализация вышеупомянутого json с использованием чистого GSON может быть выполнена:

Type sizesType = new TypeToken<List<Size>>(){}.getType();
List<Size> size = new Gson().fromJson(json, sizesType);

Но я не знаю, как сделать Retrofit 2. чтобы использовать это.

Спасибо заранее.

Ответы

Ответ 1

Забавный факт: мой код в порядке. По крайней мере, тот, который представлен в вопросе выше.

Я закончил удаление одной строки из моей модели Size.

Поскольку я сосредоточился на самом коде (особенно в настройке Retrofit), я полностью проигнорировал импорт.

Оказалось, что при реализации модели Size, когда я начал набирать класс String для полей модели:

  • name
  • createdAt
  • updatedAt

IntelliJ IDEA завершение кода предложило мне

  • not java.lang.String
  • но com.sun.org.apache.xpath.internal.operations.String

что полностью перепутано Gson десериализация.

Когда дело доходит до вознаграждения...

Я решил пометить как правильный мой собственный ответ. Зачем?

  • Чтобы гарантировать, что каждый из тех разработчиков, у кого будет такая же проблема, как и я, , убедитесь, что у вас есть действующие импортные.

Большое спасибо господам выше за их прекрасные услуги.

Поскольку у меня есть только одна награда, я решил вознаградить xiaoyaoworm, поскольку его код лучше соответствует моим потребностям (я не написал это в своем вопросе, но идея написания такого простого сервиса, как я уже представил в моем вопросе, заключается в том, чтобы скрыться от деталей реализации конечного пользователя и не использовать JsonArray и таких, как в BNK).

Обновление 1:

Единственная проблема с ответом xiaoyaoworm заключается в том, что он предложил модели Size не нужны аннотации, что полностью неправильно для цитируемого примера JSON.

В приведенном выше случае точные два поля модели Size нужны аннотации - created_at и updated_at.

Я даже тестировал несколько версий библиотеки converter-gson (я видел, что xiaoyaoworm использовал другие, чем я) - он ничего не изменил. Аннотации были необходимы.

В противном случае - снова большое спасибо!

Ответ 2

Недавно я только закончил один проект, связанный с retrofit2. Основываясь на моем источнике, я копирую все свои материалы в свой проект, чтобы попробовать, внести некоторые незначительные изменения, он хорошо работает на моей стороне.

В вашем файле build.gradle добавьте те:

 compile 'com.squareup.retrofit2:retrofit:2.0.1'
 compile 'com.google.code.gson:gson:2.6.2'
 compile 'com.squareup.okhttp3:okhttp:3.1.2'
 compile 'com.squareup.retrofit2:converter-gson:2.0.1'
 compile 'com.squareup.okhttp3:logging-interceptor:3.2.0'

Модель: (ОБНОВЛЕНИЕ: Следуйте примеру tommus, createdAt и updatedAt теперь показаны в его примере ответа json, эти два значения требуют аннотации из-за имени в модели, отличного от json respone)

public class Size {
    private int id;
    private String name;
    private boolean active;

    @SerializedName("created_at")
    private String createdAt;

    @SerializedName("updated_at")
    private String updatedAt;
}

Сервис: (Точно так же, как и у вас)

public interface service {
    @GET("sizes")
    Call<List<Size>> loadSizes();    
}

RestClient: (Я добавляю журнал здесь, чтобы вы могли видеть всю информацию о запросах и ответах, будьте осторожны, не используя Localhost, но ваш IP-адрес сервера в URL)

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.xiaoyaoworm.prolificlibrary.test.Service;

import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

public class RestClient {

    private static Service service;

    public static Service getClient() {
        if (service == null) {
            Gson gson = new GsonBuilder()
                    .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
                    .create();

            // Add logging into retrofit 2.0
            HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
            logging.setLevel(HttpLoggingInterceptor.Level.BODY);
            OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
            httpClient.interceptors().add(logging);

            Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl("http://YOURSERVERIPNOTLOCALHOST:3000/")
                    .addConverterFactory(GsonConverterFactory.create(gson))
                    .client(httpClient.build()).build();

            service = retrofit.create(Service.class);
        }
        return service;
    }
}

В своей деятельности добавьте эту функцию для запуска своего кода: (точно так же, как и вы сделали. Ответ будет вашим списком размера)

   private void loadSize() {
        Service serviceAPI = RestClient.getClient();
        Call<List<Size>> loadSizeCall = serviceAPI.loadSizes();
        loadSizeCall.enqueue(new Callback<List<Size>>() {
            @Override
            public void onResponse(Call<List<Size>> call, Response<List<Size>> response) {
                for(Size size: response.body()) {
                    System.out.println(size.toString());
                }
            }

            @Override
            public void onFailure(Call<List<Size>> call, Throwable t) {
                System.out.println(t.getMessage());
            }
        });
    }

Запустив это, вы увидите информацию, которую хотите распечатать: введите описание изображения здесь

Вот мой github repo, который я использую retrofit2.0, чтобы упростить работу GET POST PUT DELETE. Вы можете использовать это как ссылку. My Github retrofit2.0 repo

Ответ 3

Пожалуйста, используйте следующее:

build.gradle файл:

dependencies {
    ...
    compile 'com.squareup.retrofit2:retrofit:2.0.1'
    compile 'com.squareup.retrofit2:converter-gson:2.0.1'
    compile 'com.google.code.gson:gson:2.6.2'
}

WebAPIService.java:

public interface WebAPIService {
    @GET("/json.txt") // I use a simple json file to get the JSON Array as yours
    Call<JsonArray> readJsonArray();
}

Size.java:

public class Size {
    @SerializedName("id")
    private int id;

    @SerializedName("name")
    private String name;

    @SerializedName("active")
    private boolean active;

    @SerializedName("created_At")
    private String createdAt;

    @SerializedName("updated_at")
    private String updatedAt;
}

MainActivity.java:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("http://...")
            .addConverterFactory(GsonConverterFactory.create())
            .build();

    WebAPIService service = retrofit.create(WebAPIService.class);
    Call<JsonArray> jsonCall = service.readJsonArray();
    jsonCall.enqueue(new Callback<JsonArray>() {
        @Override
        public void onResponse(Call<JsonArray> call, Response<JsonArray> response) {
            String jsonString = response.body().toString();
            Log.i("onResponse", jsonString);
            Type listType = new TypeToken<List<Size>>() {}.getType();
            List<Size> yourList = new Gson().fromJson(jsonString, listType);
            Log.i("onResponse", yourList.toString());
        }

        @Override
        public void onFailure(Call<JsonArray> call, Throwable t) {
            Log.e("onFailure", t.toString());
        }
    });
}

Вот скриншот отладки:

введите описание изображения здесь


UPDATE: Вы также можете использовать следующую опцию:

@GET("/json.txt")
Call<List<Size>> readList();

и

    Call<List<Size>> listCall1 = service.readList();
    listCall1.enqueue(new Callback<List<Size>>() {
        @Override
        public void onResponse(Call<List<Size>> call, Response<List<Size>> response) {
            for (Size size : response.body()){
                Log.i("onResponse", size.toString());
            }
        }

        @Override
        public void onFailure(Call<List<Size>> call, Throwable t) {
            Log.e("onFailure", t.toString());
        }
    });