【问题标题】:Retrofit Expected BEGIN_OBJECT but was BEGIN_ARRAY改造预期为 BEGIN_OBJECT,但为 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

这里有什么问题?

【问题讨论】:

    标签: java android json gson retrofit


    【解决方案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 对象而不是字符串传递。

      【讨论】:

        猜你喜欢
        • 2015-07-24
        • 2015-11-26
        • 2018-03-27
        • 2016-06-28
        • 2019-02-06
        • 1970-01-01
        • 1970-01-01
        • 2019-03-04
        • 2017-05-16
        相关资源
        最近更新 更多