Je suis assez nouveau dans l'analyse JSON, j'utilise la bibliothèque de Retrofit de Square et j'ai rencontré ce problème.
J'essaie d'analyser cette réponse JSON:
[
{
"id": 3,
"username": "jezer",
"regid": "oiqwueoiwqueoiwqueoiwq",
"url": "http:\/\/192.168.63.175:3000\/users\/3.json"
},
{
"id": 4,
"username": "emulator",
"regid": "qwoiuewqoiueoiwqueoq",
"url": "http:\/\/192.168.63.175:3000\/users\/4.json"
},
{
"id": 7,
"username": "test",
"regid": "ksadqowueqiaksj",
"url": "http:\/\/192.168.63.175:3000\/users\/7.json"
}
]
Voici mes modèles:
public class Contacts {
public List<User> contacts;
}
...
public class User {
String username;
String regid;
@Override
public String toString(){
return(username);
}
}
mon interface:
public interface ContactsInterface {
@GET("/users.json")
void contacts(Callback<Contacts> cb);
}
ma méthode de réussite:
@Override
public void success(Contacts c, Response r) {
List<String> names = new ArrayList<String>();
for (int i = 0; i < c.contacts.size(); i++) {
String name = c.contacts.get(i).toString();
Log.d("Names", "" + name);
names.add(name);
}
ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<String>(this,
Android.R.layout.simple_spinner_item, names);
mSentTo.setAdapter(spinnerAdapter);
}
Quand je l'utilise sur ma méthode de réussite, l'erreur est renvoyée.
BEGIN_OBJECT attendu mais BEGIN_ARRAY à la ligne 1, colonne2
Qu'est-ce qui ne va pas ici?
En ce moment, vous analysez la réponse comme si elle était formatée comme ceci:
{
"contacts": [
{ .. }
]
}
L'exception vous dit ceci en ce sens que vous attendez un objet à la racine mais que les données réelles sont en réalité un tableau. Cela signifie que vous devez changer le type pour qu'il s'agisse d'un tableau.
Le moyen le plus simple consiste simplement à utiliser une liste comme type direct dans le rappel:
@GET("/users.json")
void contacts(Callback<List<User>> cb);
dans votre interface remplacer
@GET("/users.json")
void contacts(Callback<Contacts> cb);
Par ce code
@GET("/users.json")
void contacts(Callback<List<Contacts>> cb);
dependencies used :
compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.retrofit2:converter-gson:2.3.0'
les réponses json peuvent être un array response
ou un object response
ou même une combinaison des deux. Voir les trois cas suivants
Case 1 : Parsing a json array response
(cas de l'OP)
Ce cas s'applique à ceux json responses
qui sont de la forme [{...} ,{...}]
Par exemple.
[
{
"id": 3,
"username": "jezer",
"regid": "oiqwueoiwqueoiwqueoiwq",
"url": "http:\/\/192.168.63.175:3000\/users\/3.json"
},
.
.
]
Commencez par créer une classe de modèle pour ce tableau ou bien passez à goto jsonschema2pojo et générez-en automatiquement une comme ci-dessous
Contacts.Java
public class Contacts {
@SerializedName("id")
@Expose
private Integer id;
@SerializedName("username")
@Expose
private String username;
@SerializedName("regid")
@Expose
private String regid;
@SerializedName("url")
@Expose
private String url;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getRegid() {
return regid;
}
public void setRegid(String regid) {
this.regid = regid;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}
ContactsInterface
Dans ce cas, vous devriez renvoyer une liste d'objets comme suit:
public interface ContactsInterface {
@GET("/users.json")
Call<List<Contacts>> getContacts();
}
Puis appelez retrofit2
comme suit
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("baseurl_here")
.addConverterFactory(GsonConverterFactory.create())
.build();
ContactsInterface request = retrofit.create(ContactsInterface.class);
Call<List<Contacts>> call = request.getContacts();
call.enqueue(new Callback<List<Contacts>>() {
@Override
public void onResponse(Call<List<Contacts>> call, Response<List<Contacts>> response) {
Toast.makeText(MainActivity.this,response.body().toString(),Toast.LENGTH_SHORT).show();
}
@Override
public void onFailure(Call<List<Contacts>> call, Throwable t) {
Log.e("Error",t.getMessage());
}
});
response.body()
vous donnera la liste des objets
VOUS POUVEZ ÉGALEMENT CONSULTER LES DEUX CAS SUIVANTS POUR RÉFÉRENCE
Case 2 : Parsing a json object response
Ce cas s’applique aux réponses JSON qui se présentent sous la forme {..}
Par exemple.
{
"id": 3,
"username": "jezer",
"regid": "oiqwueoiwqueoiwqueoiwq",
"url": "http:\/\/192.168.63.175:3000\/users\/3.json"
}
Ici, nous avons la même variable object
que ci-dessus. Donc, la classe de modèle sera la même, mais comme dans l'exemple ci-dessus, nous n'avons pas un tableau de ces objets - juste un seul objet et, par conséquent, nous n'avons pas besoin de l'analyser sous forme de liste.
Alors apportez les modifications suivantes pour un object response
public interface ContactsInterface {
@GET("/users.json")
Call<Contacts> getContacts();
}
Puis appelez retrofit2
comme suit
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("baseurl_here")
.addConverterFactory(GsonConverterFactory.create())
.build();
ContactsInterface request = retrofit.create(ContactsInterface.class);
Call<Contacts> call = request.getContacts();
call.enqueue(new Callback<Contacts>() {
@Override
public void onResponse(Call<Contacts> call, Response<Contacts> response) {
Toast.makeText(MainActivity.this,response.body().toString(),Toast.LENGTH_SHORT).show();
}
@Override
public void onFailure(Call<Contacts> call, Throwable t) {
Log.e("Error",t.getMessage());
}
});
response.body()
vous donnera l'objet
Vous pouvez également vérifier une erreur commune lors de l'analyse de la réponse à un objet Json: "attendue begin_array mais était begin_object"
Case 3 : Parsing a json array inside json object
Ce cas s'applique à ceux json responses
qui sont de la forme {"array_name":[{...} ,{...}]}
Par exemple.
{
"contacts":
[
{
"id": 3,
"username": "jezer",
"regid": "oiqwueoiwqueoiwqueoiwq",
"url": "http:\/\/192.168.63.175:3000\/users\/3.json"
}
]
}
Vous aurez besoin de deux classes de modèle ici car nous avons deux objets (un à l'extérieur et un à l'intérieur du tableau). Générez-le comme ci-dessous
ContactWrapper
public class ContactWrapper {
@SerializedName("contacts")
@Expose
private List<Contacts> contacts = null;
public List<Contacts> getContacts() {
return contacts;
}
public void setContacts(List<Contacts> contacts) {
this.contacts = contacts;
}
}
Vous pouvez utiliser Contacts.Java
généré ci-dessus pour les objets de liste (généré pour le cas 1)
Alors apportez les modifications suivantes pour un object response
public interface ContactsInterface {
@GET("/users.json")
Call<ContactWrapper> getContacts();
}
Puis appelez retrofit2
comme suit
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("baseurl_here")
.addConverterFactory(GsonConverterFactory.create())
.build();
ContactsInterface request = retrofit.create(ContactsInterface.class);
Call<ContactWrapper> call = request.getContacts();
call.enqueue(new Callback<ContactWrapper>() {
@Override
public void onResponse(Call<ContactWrapper> call, Response<ContactWrapper> response) {
Toast.makeText(MainActivity.this,response.body().getContacts().toString(),Toast.LENGTH_SHORT).show();
}
@Override
public void onFailure(Call<ContactWrapper> call, Throwable t) {
Log.e("Error",t.getMessage());
}
});
Ici, la différence avec le cas 1 est que nous devrions utiliser response.body().getContacts()
au lieu de response.body()
pour obtenir la liste des objets.
Quelques références pour les cas ci-dessus:
case 1: Analyse d'une réponse de tableau JSON , cas 2: Analyse d'une réponse d'objet Json , mixed: Analyse de tableau Json dans un autre objet JSON
Convertissez-le en une liste.
Voici l'exemple:
BenchmarksListModel_v1[] benchmarksListModel = res.getBody().as(BenchmarksListModel_v1[].class);
Code source fonctionnant
https://drive.google.com/open?id=0BzBKpZ4nzNzUVFRnVVzz0JabUU
public interface ApiInterface {
@GET("inbox.json")
Call<List<Message>> getInbox();
}
call.enqueue(new Callback<List<Message>>() {
@Override
public void onResponse(Call<List<Message>> call, Response<List<Message>> response) {
YourpojoClass.addAll(response.body());
mAdapter.notifyDataSetChanged();
}
@Override
public void onFailure(Call<List<Message>> call, Throwable t) {
Toast.makeText(getApplicationContext(), "Unable to fetch json: " + t.getMessage(), Toast.LENGTH_LONG).show();
}
});
En utilisantMPV, dans votre désérialiseur, mettez ceci
JsonObject obj = new JsonObject();
obj.add("data", json);
JsonArray data = obj.getAsJsonObject().getAsJsonArray("data");
La pile ici est Kotlin, Retrofit2, RxJava et nous migrons vers cette méthode hors des méthodes Call
normales.
Le service que j'avais créé jetait com.google.gson.JsonSyntaxException
et Java.lang.IllegalStateException
avec le message suivant:
Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column2
Mais toutes les réponses que j'ai pu trouver indiquaient que cela était dû à l'absence de type array dans le service, ce que j'avais déjà fait. Mon service Kotlin ressemblait à ceci:
// Data class. Retrofit2 & Gson can deserialize this. No extra code needed.
data class InventoryData(
val productCode: String,
val stockDate: String,
val availCount: Int,
val totalAvailCount: Int,
val inventorySold: Int,
val closed: Boolean
)
// BROKEN SERVICE. Throws com.google.gson.JsonSyntaxException
// Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column2
interface InventoryService {
@GET("getInventoryData/{storeId}")
fun getInventoryData(@Path("storeId") storeId: String,
@Query("startDate") startDate: String,
@Query("endDate") endDate: String) :
Result<Single<List<InventoryData>>>
}
Le problème était la Result
dedans, que j'avais mise lorsque j'utilisais une solution antérieure basée sur Call
.
Le supprimer a résolu le problème. J'ai également dû modifier la signature des deux méthodes de traitement des erreurs sur mon site d'appels pour le service:
/// WORKING SERVICE
interface InventoryService {
@GET("getInventoryData/{storeId}")
fun getInventoryData(@Path("storeId") storeId: String,
@Query("startDate") startDate: String,
@Query("endDate") endDate: String) :
Single<List<InventoryData>>
}
Et le code de fragment de site d’appel qui utilise le service:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.disposables
.add(viewModel.ratsService.getInventoryData(it, fromDate, toDate)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(this::successResult, this::failureResult))
}
}
private fun failureResult(error: Throwable) {
when (error) {
is HttpException -> { if (error.code() == 401) {
textField.text = "Log in required!" } }
else -> textField.text = "Error: $error."
}
}
/// Had to change to this from previous broken
/// one that took `Result<List<InventoryData>>`
private fun successResult(result: List<InventoryData>) {
textField.text = result.toString()
}
Notez que le code ci-dessus a été légèrement modifié. En particulier, j'ai utilisé Retrofit2 ConverterFactory
pour permettre aux dates d'être transmises sous forme d'objets OffsetDateTime au lieu de chaînes.