改造预期为 BEGIN_OBJECT,但为 BEGIN_ARRAY

Posted

技术标签:

【中文标题】改造预期为 BEGIN_OBJECT,但为 BEGIN_ARRAY【英文标题】:Retrofit Expected BEGIN_OBJECT but was BEGIN_ARRAY 【发布时间】:2014-08-01 00:54:45 【问题描述】:

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

我正在尝试解析这个 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 行第 2 列是 BEGIN_ARRAY

这里有什么问题?

【问题讨论】:

【参考方案1】:

现在您正在解析响应,就好像它的格式如下:


  "contacts": [
     .. 
  ]

该异常告诉您这一点,因为您期望在根处有一个对象,但实际数据实际上是一个数组。这意味着您需要将类型更改为数组。

最简单的方法是在回调中使用列表作为直接类型:

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

【讨论】:

@AzlanJamal 以同样的方式【参考方案2】:

dependencies used :

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

json 响应可以是 array responseobject response 甚至两者的组合。请看以下三种情况

Case 1 : Parsing a json array response(OP 案例)

这种情况适用于格式为 [... ,...]

json responses

例如

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

首先为这个数组创建一个模型类,或者直接转到jsonschema2pojo 并自动生成一个如下所示

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

在这种情况下,您应该返回一个对象列表,如下所示

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

然后像下面这样调用retrofit2

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() 会给你对象列表

您还可以查看以下两个案例以供参考

Case 2 : Parsing a json object response

这种情况适用于格式为 ..

的 json 响应

例如


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

在这里,我们有与上面示例相同的object。所以模型类将是相同的,但就像上面的示例一样,我们没有这些对象的数组 - 只有一个对象,因此我们不需要将其解析为列表。

所以对object response进行以下更改

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

然后像下面这样调用retrofit2

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() 会给你对象

您还可以在解析 json 对象响应时检查常见错误:"expected begin_array but was begin_object"

Case 3 : Parsing a json array inside json object

这种情况适用于格式为 "array_name":[... ,...]

json responses

例如

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

这里需要两个模型类,因为我们有两个对象(一个在数组外部,一个在数组内部)。如下所示生成它

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;



您可以将上面生成的Contacts.java用于列表对象(为案例1生成)

所以对object response进行以下更改

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

然后像下面这样调用retrofit2

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

这里与案例1的区别在于我们应该使用response.body().getContacts()而不是response.body()来获取对象列表

以上案例的一些参考:

案例 1:Parsing a json array response, 案例2:Parsing a json object response, 混合:Parsing json array inside another json object

【讨论】:

hai 我正在尝试请求以第 3 格式获取数据,但我不断收到相同的错误,您能帮帮我吗 请提供有关您的代码的更多信息,同时确保您在模型类中的数组名称拼写与响应的拼写完全匹配【参考方案3】:

在您的界面中 替换

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

通过此代码

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

【讨论】:

这似乎是相同的答案减去 Jake Wharton 发布的描述。 是的,我有同样的错误,在修改pojo之前我决定创建一个arrayList并得到相同的结果【参考方案4】:

将其转换为列表。

下面是例子:

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

【讨论】:

【参考方案5】:

源代码工作

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

【讨论】:

我已经尝试过这样但我得到 response.body() as [ ] empty....没有数据请帮助我【参考方案6】:

使用MPV,在你的反序列化器中,把这个

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

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

【讨论】:

请解释为什么这样可以解决问题 如果服务器响应具有像 [..] 这样的 JSON 格式,则改造无法正确迭代,必须在 JSON 上放置前缀,正如 Jake Wharton 建议的那样。最终的 JSON 将是这样的 "data":[..] 并解决了问题。【参考方案7】:

这里的堆栈是 Kotlin、Retrofit2、RxJava,我们正在迁移到常规的 Call 方法。

我创建的服务是用消息抛出com.google.gson.JsonSyntaxExceptionjava.lang.IllegalStateException

Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column2

但我能找到的所有答案都说这是由于服务中没有 array 类型造成的,而我已经这样做了。我的 Kotlin 服务如下所示:

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

问题在于其中的Result,这是我在使用早期基于Call 的解决方案时放入的。

删除它可以解决问题。我还必须在我的服务调用站点更改两种错误处理方法的签名:

/// WORKING SERVICE
interface InventoryService 

    @GET("getInventoryData/storeId")
    fun getInventoryData(@Path("storeId") storeId: String,
                         @Query("startDate") startDate: String,
                         @Query("endDate") endDate: String) :
            Single<List<InventoryData>>

以及使用该服务的调用站点片段代码:

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

请注意,上面的代码做了一点改动。特别是我使用了 Retrofit2 ConverterFactory 来允许将日期作为 OffsetDateTime 对象而不是字符串传递。

【讨论】:

以上是关于改造预期为 BEGIN_OBJECT,但为 BEGIN_ARRAY的主要内容,如果未能解决你的问题,请参考以下文章

预期 BEGIN_OBJECT 但在第 1 行第 1 列路径为 STRING - Laravel 到改造 2

预期为 BEGIN_ARRAY,但在第 1 行第 2 列改造时为 BEGIN_OBJECT2

如何修复预期的字符串,但在带有改造的嵌套数组上是 BEGIN_OBJECT

改造后请求错误 - java.lang.IllegalStateException: 预期 BEGIN_OBJECT 但在第 1 行第 1 列路径 $

快速 API 错误:预期 BEGIN_ARRAY 但 BEGIN_OBJECT 在第 1 行第 2 列 PATH $ 通过使用改造

改造预期的 begin_array 但在第 1 行第 2 列路径 $ Android Studio 处是 begin_object