Ich bin relativ neu in der JSON-Analyse, ich verwende die Retrofit-Bibliothek von Square und bin auf dieses Problem gestoßen.
Ich versuche diese JSON-Antwort zu analysieren:
[
{
"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"
}
]
Hier sind meine Modelle:
public class Contacts {
public List<User> contacts;
}
...
public class User {
String username;
String regid;
@Override
public String toString(){
return(username);
}
}
meine Schnittstelle:
public interface ContactsInterface {
@GET("/users.json")
void contacts(Callback<Contacts> cb);
}
meine erfolgsmethode:
@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);
}
Wenn ich es für meine Erfolgsmethode verwende, wird der Fehler ausgegeben
Erwartete BEGIN_OBJECT, war jedoch in Zeile 1 Spalte2 BEGIN_ARRAY
Was ist hier falsch?
Jetzt analysieren Sie die Antwort so, als wäre sie so formatiert:
{
"contacts": [
{ .. }
]
}
Die Ausnahme sagt Ihnen, dass Sie ein Objekt im Stammverzeichnis erwarten, die realen Daten jedoch tatsächlich ein Array sind. Das bedeutet, dass Sie den Typ in ein Array ändern müssen.
Am einfachsten ist es, eine Liste als direkten Typ im Rückruf zu verwenden:
@GET("/users.json")
void contacts(Callback<List<User>> cb);
in Ihrer Schnittstelle ersetzen
@GET("/users.json")
void contacts(Callback<Contacts> cb);
Mit diesem 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'
json-Antworten können ein array response
oder ein object response
oder auch eine Kombination aus beiden sein. Siehe die folgenden drei Fälle
Case 1 : Parsing a json array response
(Fall des OP)
Dieser Fall trifft auf diejenigen json responses
zu, die die Form [{...} ,{...}]
haben.
Z.B.
[
{
"id": 3,
"username": "jezer",
"regid": "oiqwueoiwqueoiwqueoiwq",
"url": "http:\/\/192.168.63.175:3000\/users\/3.json"
},
.
.
]
Erstellen Sie zuerst eine Modellklasse für dieses Array oder einfach goto jsonschema2pojo und generieren Sie eine wie unten beschrieben
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
In diesem Fall sollten Sie eine Liste von Objekten wie die folgende zurückgeben
public interface ContactsInterface {
@GET("/users.json")
Call<List<Contacts>> getContacts();
}
Dann rufen Sie den retrofit2
-Aufruf wie folgt auf
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()
gibt Ihnen die Liste der Objekte
SIE KÖNNEN AUCH DIE FOLGENDEN ZWEI FÄLLE AUF VERWEIS AUFNEHMEN
Case 2 : Parsing a json object response
Dieser Fall trifft auf json-Antworten zu, die die Form {..} haben.
Z.B.
{
"id": 3,
"username": "jezer",
"regid": "oiqwueoiwqueoiwqueoiwq",
"url": "http:\/\/192.168.63.175:3000\/users\/3.json"
}
Hier haben wir die gleiche object
wie im obigen Beispiel. Die Modellklasse ist also dieselbe, aber wie im obigen Beispiel haben wir kein Array dieser Objekte - nur ein einzelnes Objekt, und wir müssen es nicht als Liste analysieren.
Nehmen Sie also die folgenden Änderungen für einen object response
vor.
public interface ContactsInterface {
@GET("/users.json")
Call<Contacts> getContacts();
}
Dann rufen Sie den retrofit2
-Aufruf wie folgt auf
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()
gibt Ihnen das Objekt
Sie können auch einen allgemeinen Fehler überprüfen, während Sie die Antwort des Json-Objekts analysieren: "erwartet begin_array, aber war begin_object"
Case 3 : Parsing a json array inside json object
Dieser Fall trifft auf diejenigen json responses
zu, die die Form {"array_name":[{...} ,{...}]}
haben.
Z.B.
{
"contacts":
[
{
"id": 3,
"username": "jezer",
"regid": "oiqwueoiwqueoiwqueoiwq",
"url": "http:\/\/192.168.63.175:3000\/users\/3.json"
}
]
}
Sie benötigen hier zwei Modellklassen, da wir zwei Objekte haben (eines außerhalb und eines innerhalb des Arrays). Generieren Sie es wie folgt
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;
}
}
Sie können Contacts.Java
verwenden, das oben für die Listenobjekte generiert wurde (generiert für Fall 1).
Nehmen Sie also die folgenden Änderungen für einen object response
vor.
public interface ContactsInterface {
@GET("/users.json")
Call<ContactWrapper> getContacts();
}
Dann rufen Sie den retrofit2
-Aufruf wie folgt auf
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());
}
});
Der Unterschied zu Fall 1 besteht darin, dass wir response.body().getContacts()
anstelle von response.body()
verwenden sollten, um die Liste der Objekte abzurufen
Einige Referenzen für die oben genannten Fälle:
case 1: Parsing einer Antwort eines Json-Arrays , Case 2: Parsen einer Antwort eines Json-Objekts , mixed: Parsen eines Json-Arrays in einem anderen Json-Objekt
Konvertieren Sie es in eine Liste.
Unten ist das Beispiel:
BenchmarksListModel_v1[] benchmarksListModel = res.getBody().as(BenchmarksListModel_v1[].class);
Quellcode funktioniert
https://drive.google.com/open?id=0BzBKpZ4nzNzUVFRnVVkzc0JabUU
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();
}
});
Verwenden SieMPVin Ihrem Deserializer
JsonObject obj = new JsonObject();
obj.add("data", json);
JsonArray data = obj.getAsJsonObject().getAsJsonArray("data");
Der Stack hier ist Kotlin, Retrofit2, RxJava und wir migrieren zu jenen aus regulären Call
Methoden.
Der Dienst, den ich erstellt hatte, war com.google.gson.JsonSyntaxException
und Java.lang.IllegalStateException
mit der Meldung zu werfen:
Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column2
Aber alle Antworten, die ich finden konnte, besagten, dass dies darauf zurückzuführen war, dass der Dienst nicht mit einem Array - Typ versehen war, was ich bereits getan habe. Mein Kotlin-Service sah so aus:
// 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>>>
}
Das Problem war der Result
, den ich eingegeben hatte, als ich eine frühere Call
-basierte Lösung verwendete.
Durch das Entfernen wurde das Problem behoben. Ich musste auch die Signatur der beiden Fehlerbehandlungsmethoden an meiner Call-Site für den Service ändern:
/// WORKING SERVICE
interface InventoryService {
@GET("getInventoryData/{storeId}")
fun getInventoryData(@Path("storeId") storeId: String,
@Query("startDate") startDate: String,
@Query("endDate") endDate: String) :
Single<List<InventoryData>>
}
Und der Call-Site-Fragmentcode, der den Dienst verwendet:
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()
}
Beachten Sie, dass der obige Code ein wenig geändert wurde. Insbesondere habe ich Retrofit2 ConverterFactory
verwendet, um zu ermöglichen, dass die Daten als OffsetDateTime-Objekte anstelle von Zeichenfolgen übergeben werden.