修复预期的 BEGIN_OBJECT 但是是 BEGIN_ARRAY

我对 JSON 解析相当陌生,我正在使用 Square 的 Revifit 库,遇到了这个问题。

我试图解析这个 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"
}
]

以下是我的模型:

public class Contacts {


public List<User> contacts;


}

...

public class User {


String username;
String regid;


@Override
public String toString(){
return(username);
}


}

我的界面:

public interface ContactsInterface {


@GET("/users.json")
void contacts(Callback<Contacts> cb);


}

我的成功之道:

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

当我在我的成功方法中使用它时,它会抛出错误

预期 BEGIN _ OBJECT,但是在第1行 column 2处为 BEGIN _ ARRAY

怎么了?

93436 次浏览

Right now you are parsing the response as if it was formatted like this:

{
"contacts": [
{ .. }
]
}

The exception tells you this in that you are expecting an object at the root but the real data is actually an array. This means you need to change the type to be an array.

The easiest way is to just use a list as the direct type in the callback:

@GET("/users.json")
void contacts(Callback<List<User>> cb);

Convert it into a list.

Below is the example:

BenchmarksListModel_v1[] benchmarksListModel = res.getBody().as(BenchmarksListModel_v1[].class);

in your interface replace

@GET("/users.json")
void contacts(Callback<Contacts> cb);

By this code

@GET("/users.json")
void contacts(Callback<List<Contacts>> cb);

Source Code Working

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

Using MPV, in your Deserializer, put this

JsonObject obj = new JsonObject();
obj.add("data", json);


JsonArray data  = obj.getAsJsonObject().getAsJsonArray("data");

dependencies used :

compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.retrofit2:converter-gson:2.3.0'

json responses can be an array response or an object response or even a combination of both. See the following three cases

Case 1 : Parsing a json array response (OP's case)

This case applies to those json responses which are of the form [{...} ,{...}]

E.g.

[
{
"id": 3,
"username": "jezer",
"regid": "oiqwueoiwqueoiwqueoiwq",
"url": "http:\/\/192.168.63.175:3000\/users\/3.json"
},
.
.
]

First create a model class for this array or just goto jsonschema2pojo and auto generate one like below

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 this case you should return a list of objects like the following

public interface ContactsInterface {
@GET("/users.json")
Call<List<Contacts>> getContacts();
}

Then make the retrofit2 call like the following

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() will give you the list of objects

YOU MAY ALSO CHECK THE FOLLOWING TWO CASES FOR REFERENCE

Case 2 : Parsing a json object response

This case applies to those json responses which are of the form {..}

E.g.

{
"id": 3,
"username": "jezer",
"regid": "oiqwueoiwqueoiwqueoiwq",
"url": "http:\/\/192.168.63.175:3000\/users\/3.json"
}

Here, we have the same object as above example. So the model class will be the same, but like above example we dont have an array of these objects - just one single object and hence we dont need to parse it as a list.

So make the following changes for an object response

public interface ContactsInterface {
@GET("/users.json")
Call<Contacts> getContacts();
}

Then make the retrofit2 call like the following

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() will give you the object

You may also check a common error while parsing json object response :"expected begin_array but was begin_object"

Case 3 : Parsing a json array inside json object

This case applies to those json responses which are of the form {"array_name":[{...} ,{...}]}

E.g.

    {
"contacts":
[
{
"id": 3,
"username": "jezer",
"regid": "oiqwueoiwqueoiwqueoiwq",
"url": "http:\/\/192.168.63.175:3000\/users\/3.json"
}
]
}

You will need two model classes here since we have two objects(one outside and one inside the array).Generate it like below

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


}

You can use Contacts.java generated above for the list objects(generated for case 1)

So make the following changes for an object response

public interface ContactsInterface {
@GET("/users.json")
Call<ContactWrapper> getContacts();
}

Then make the retrofit2 call like the following

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

Here, the difference from case 1 is that we should use response.body().getContacts() instead of response.body() to get the list of objects

Some references for above cases :

case 1 : Parsing a json array response, case 2 : Parsing a json object response, mixed : Parsing json array inside another json object

The stack here is Kotlin, Retrofit2, RxJava and we're migrating to that off regular Call methods.

The service that I had created was throwing com.google.gson.JsonSyntaxException and java.lang.IllegalStateException with message:

Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column2

But all the answers I could find said that this was caused by not having an array type in the service, which I already did. My Kotlin service looked like this:

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

The problem was the Result in there, which I had put in when I was using an earlier Call based solution.

Removing it fixed the problem. I also had to change the signature of the two error handling methods at my call-site for the 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>>
}

And the call-site fragment code that uses the 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()
}

Note that the above code has been changed a little. In particular I used a Retrofit2 ConverterFactory to allow the dates to be passed in as OffsetDateTime objects instead of strings.