web-dev-qa-db-fra.com

Comment analyser la liste des objets JSON entourés de [] à l'aide de Retrofit et GSON?

J'ai créé un simple point de terminaison REST:

http://<server_address>:3000/sizes

Cette URL renvoie une réponse très simple contenant un tableau json comme suit:

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

Maintenant, j'essaie de consommer cette réponse en utilisant Retrofit 2 avec GSON.

J'ai ajouté un modèle:

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

Et service:

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

J'ai instancié une rénovation:

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

Et mon service:

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

Maintenant, essayons d'appeler les données:

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

Ce qui se termine par une exception:

Java.lang.IllegalStateException: BEGIN_OBJECT attendu mais était STRING à la ligne 1 colonne 18 chemin $ [0] .name

Je suppose que l'erreur est causée par cela, le l'API REST renvoie une réponse qui est un tableau ni un objet.

  1. Ai-je raison?
  2. Quelle est la façon la plus simple de faire fonctionner ce code?

le service REST ne peut pas être modifié, donc la réponse doit rester telle quelle.

De plus, la désérialisation du json ci-dessus à l'aide de GSON pur peut être effectuée par:

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

Mais je n'ai aucune idée de comment faire Retrofit 2 pour l'utiliser.

Merci d'avance.

25
Tomasz Dzieniak

Le fait drôle est ... Mon code est parfaitement bien. Au moins celui présenté dans la question ci-dessus.

J'ai fini par supprimer une ligne de mon modèle Size.

Comme je me suis concentré sur le code lui-même (en particulier la configuration de Retrofit), j'ai totalement ignoré les importations.

Il s'est avéré - lors de l'implémentation du modèle Size lorsque j'ai commencé à taper la classe String pour les champs du modèle:

  • name
  • createdAt
  • updatedAt

La complétion de code IntelliJ IDEA m'a suggéré

  • ne pas Java.lang.String
  • mais com.Sun.org.Apache.xpath.internal.operations.String

ce qui a totalement gâché la désérialisation de Gson.

En ce qui concerne les récompenses ...

J'ai décidé de marquer comme valide ma propre réponse. Pourquoi?

  • Pour vous assurer que chacun de ces développeurs, qui viendra avec exactement le même problème que moi - assurez-vous d'avoir des importations valides .

Un grand merci aux messieurs ci-dessus pour leurs excellents services.

Comme je n'ai qu'une seule prime, j'ai décidé de récompenser xiaoyaoworm car son code correspond mieux à mes besoins (je ne l'ai pas écrit dans ma question mais l'idée d'écrire un service aussi simple - comme je l'ai présenté dans ma question - est de se cacher des détails d'implémentation de l'utilisateur final et de ne pas utiliser JsonArray et autres comme dans BNK réponse).

Mise à jour 1:

Le seul problème avec xiaoyaoworm est que, il a suggéré que le modèle Size ne nécessite aucune annotation ce qui est totalement faux pour l'exemple JSON cité.

Pour le cas ci-dessus, exactement deux champs du modèle Size nécessitent des annotations - created_at et updated_at.

J'ai même testé quelques versions du converter-gson bibliothèque (j'ai vu xiaoyaoworm avoir utilisé autre chose que moi) - cela n'a rien changé. Des annotations étaient nécessaires.

Sinon - encore une fois, merci beaucoup!

5
Tomasz Dzieniak

Récemment, je viens de terminer un projet lié à retrofit2. Sur la base de ma source, je copie tous vos trucs dans mon projet pour essayer, en faisant quelques changements mineurs, cela fonctionne bien de mon côté.

Dans votre build.gradle, ajoutez ceux-ci:

 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'

Modèle: ( MISE À JOUR: Suivez le cas de tommus, createdAt et updatedAt est maintenant affiché dans son exemple de réponse json, ces deux valeurs doivent être annotées car le nom dans le modèle est différent de 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;
}

Service: (Exactement le même que ce que vous avez)

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

RestClient: ( J'ajoute un journal ici, afin que vous puissiez voir toutes les informations sur les demandes et les réponses, faites attention de ne pas utiliser Localhost mais l'adresse IP de votre serveur dans l'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;
    }
}

Sur votre activité, ajoutez cette fonction pour exécuter votre code: ( exactement comme ce que vous avez fait. La réponse sera votre liste de taille )

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

En exécutant cela, vous verrez les informations que vous souhaitez imprimer: enter image description here

Voici mon dépôt github que j'utilise retrofit2.0 pour faire simple GET POST PUT DELETE fonctionne. Vous pouvez l'utiliser comme référence. Mon repo Github retrofit2.

19
xiaoyaoworm

Veuillez utiliser ce qui suit:

fichier 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();
}

Taille.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());
        }
    });
}

Voici la capture d'écran de débogage:

enter image description here


MISE À JOUR: Vous pouvez également utiliser l'option suivante:

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

et

    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());
        }
    });
7
BNK