【问题标题】:How to parse list of JSON objects surrounded by [] using Retrofit and GSON?如何使用 Retrofit 和 GSON 解析 [] 包围的 JSON 对象列表?
【发布时间】:2016-08-07 23:57:06
【问题描述】:

我创建了一个简单的 REST 端点:

http://<server_address>:3000/sizes

此 URL 返回一个非常简单的响应,其中包含一个 json 数组,如下所示:

[
  { "id": 1, "name": "Small", "active": true },
  { "id": 2, "name": "Medium", "active": true },
  { "id": 3, "name": "Large", "active": true }
]

现在,我正在尝试使用带有 GSON 的 Retrofit 2 来处理此响应

我添加了一个模型:

@lombok.AllArgsConstructor
@lombok.EqualsAndHashCode
@lombok.ToString
public class Size {
    private int id;
    private String name;
    private boolean active;

    @SerializedName("created_at")
    private String createdAt;

    @SerializedName("updated_at")
    private String updatedAt;
}

和服务:

public interface Service {
    @GET("sizes")
    Call<List<Size>> loadSizes();
}

我已经实例化了一个 Retrofit:

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("http://<server_address>:3000")
    .addConverterFactory(GsonConverterFactory.create())
    .build();

还有我的服务:

Service service = retrofit.create(Service.class);

现在,尝试调用数据:

service.loadSizes().enqueue(new Callback<List<Size>>() {
    @Override
    public void onResponse(Call<List<Size>> call, Response<List<Size>> response) {
        for(Size size: response.body()) {
            System.out.println(size.toString());
        }
    }

    @Override
    public void onFailure(Call<List<Size>> call, Throwable t) {
        System.out.println(t.getMessage());
    }
});

最后会出现什么异常:

java.lang.IllegalStateException应为 BEGIN_OBJECT,但在第 1 行第 18 列路径 $[0].name 处为 STRING

我想这个错误是由这个引起的,REST API 返回的响应既不是数组也不是对象

  1. 我说的对吗?
  2. 让这段代码工作的最简单方法是什么?

REST 服务无法修改,因此响应必须保持原样。

此外,使用纯 GSON 对上述 json 进行反序列化可以通过以下方式完成:

Type sizesType = new TypeToken<List<Size>>(){}.getType();
List<Size> size = new Gson().fromJson(json, sizesType);

但我不知道如何让 Retrofit 2 使用它。

提前致谢。

【问题讨论】:

  • 你试过没有lombok注解吗?也许他们以某种方式让 GSON 认为 name 是一个对象,而不是一个字符串?
  • 是的,我试过了。没有变化,同样的错误。

标签: java json rest gson retrofit2


【解决方案1】:

最近我刚刚完成了一个与改造 2 相关的项目。根据我的来源,我将你所有的东西复制到我的项目中尝试一下,做了一些小的改动,它对我来说效果很好。

在你的 build.gradle 中,添加这些:

 compile 'com.squareup.retrofit2:retrofit:2.0.1'
 compile 'com.google.code.gson:gson:2.6.2'
 compile 'com.squareup.okhttp3:okhttp:3.1.2'
 compile 'com.squareup.retrofit2:converter-gson:2.0.1'
 compile 'com.squareup.okhttp3:logging-interceptor:3.2.0'

Model: (UPDATE: 按照tommus的情况,createdAt和updatedAt现在显示在他的json响应示例中,这两个值需要注释,因为模型中的名称与json响应不同)

public class Size {
    private int id;
    private String name;
    private boolean active;

    @SerializedName("created_at")
    private String createdAt;

    @SerializedName("updated_at")
    private String updatedAt;
}

服务:(与您拥有的完全相同)

public interface service {
    @GET("sizes")
    Call<List<Size>> loadSizes();    
}

RestClient: (我在这里添加log,这样你就可以看到所有的请求信息和响应信息,注意不要使用Localhost,而是你的服务器IP地址在URL中 )

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.xiaoyaoworm.prolificlibrary.test.Service;

import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

public class RestClient {

    private static Service service;

    public static Service getClient() {
        if (service == null) {
            Gson gson = new GsonBuilder()
                    .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
                    .create();

            // Add logging into retrofit 2.0
            HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
            logging.setLevel(HttpLoggingInterceptor.Level.BODY);
            OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
            httpClient.interceptors().add(logging);

            Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl("http://YOURSERVERIPNOTLOCALHOST:3000/")
                    .addConverterFactory(GsonConverterFactory.create(gson))
                    .client(httpClient.build()).build();

            service = retrofit.create(Service.class);
        }
        return service;
    }
}

在您的活动中,添加此函数以运行您的代码:(与您所做的完全相同。响应将是您的大小列表

   private void loadSize() {
        Service serviceAPI = RestClient.getClient();
        Call<List<Size>> loadSizeCall = serviceAPI.loadSizes();
        loadSizeCall.enqueue(new Callback<List<Size>>() {
            @Override
            public void onResponse(Call<List<Size>> call, Response<List<Size>> response) {
                for(Size size: response.body()) {
                    System.out.println(size.toString());
                }
            }

            @Override
            public void onFailure(Call<List<Size>> call, Throwable t) {
                System.out.println(t.getMessage());
            }
        });
    }

运行此程序,您将看到要打印的信息:

这是我的 github 存储库,我使用 retrofit2.0 进行简单的 GET POST PUT DELETE 工作。您可以将此作为参考。 My Github retrofit2.0 repo

【讨论】:

  • 也许覆盖 toString 是更好的类 Size :)
【解决方案2】:

请使用以下内容:

build.gradle 文件:

dependencies {
    ...
    compile 'com.squareup.retrofit2:retrofit:2.0.1'
    compile 'com.squareup.retrofit2:converter-gson:2.0.1'
    compile 'com.google.code.gson:gson:2.6.2'
}

WebAPIService.java:

public interface WebAPIService {
    @GET("/json.txt") // I use a simple json file to get the JSON Array as yours
    Call<JsonArray> readJsonArray();
}

大小.java:

public class Size {
    @SerializedName("id")
    private int id;

    @SerializedName("name")
    private String name;

    @SerializedName("active")
    private boolean active;

    @SerializedName("created_At")
    private String createdAt;

    @SerializedName("updated_at")
    private String updatedAt;
}

MainActivity.java:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("http://...")
            .addConverterFactory(GsonConverterFactory.create())
            .build();

    WebAPIService service = retrofit.create(WebAPIService.class);
    Call<JsonArray> jsonCall = service.readJsonArray();
    jsonCall.enqueue(new Callback<JsonArray>() {
        @Override
        public void onResponse(Call<JsonArray> call, Response<JsonArray> response) {
            String jsonString = response.body().toString();
            Log.i("onResponse", jsonString);
            Type listType = new TypeToken<List<Size>>() {}.getType();
            List<Size> yourList = new Gson().fromJson(jsonString, listType);
            Log.i("onResponse", yourList.toString());
        }

        @Override
        public void onFailure(Call<JsonArray> call, Throwable t) {
            Log.e("onFailure", t.toString());
        }
    });
}

这是调试截图:


更新: 您还可以使用以下选项:

@GET("/json.txt")
Call<List<Size>> readList();

    Call<List<Size>> listCall1 = service.readList();
    listCall1.enqueue(new Callback<List<Size>>() {
        @Override
        public void onResponse(Call<List<Size>> call, Response<List<Size>> response) {
            for (Size size : response.body()){
                Log.i("onResponse", size.toString());
            }
        }

        @Override
        public void onFailure(Call<List<Size>> call, Throwable t) {
            Log.e("onFailure", t.toString());
        }
    });

【讨论】:

    【解决方案3】:

    有趣的事实是……我的代码非常好。至少是上述问题中提出的那个。

    我最终从我的 Size 模型中删除了一行。

    当我专注于代码本身(尤其是 Retrofit 的配置)时,我完全忽略了导入。

    结果是——在实现Size 模型时,我开始为模型的字段输入String 类:

    • name
    • createdAt
    • updatedAt

    IntelliJ IDEA 的代码补全建议我

    • 不是java.lang.String
    • 但是com.sun.org.apache.xpath.internal.operations.String

    Gson 的反序列化完全搞砸了

    说到奖励……

    我决定将我自己的答案标记为有效。为什么?

    • 为确保每个开发人员都会遇到与我完全相同的问题 - 确保您有有效的导入

    非常感谢上面的先生们提供的优质服务。

    由于我只有一个赏金,我决定奖励xiaoyaoworm,因为他的代码更符合我的需求(我没有在我的问题中写下它,而是写这样简单的服务的想法 - 正如我在我的问题 - 是隐藏最终用户的实现细节,而不是在BNK 响应中使用JsonArray 等)。

    更新 1:

    xiaoyaoworm 的答案的唯一问题是,他建议 Size 模型不需要任何注释引用的 JSON 示例完全错误。

    对于上述情况,Size 模型的两个字段需要注释 - created_atupdated_at

    我什至测试了几个版本的converter-gson 库(我看到xiaoyaoworm 除了我之外还使用过)——它没有改变任何东西。注释是必要的。

    否则 - 再次,非常感谢!

    【讨论】:

    • 当我复制粘贴您的代码时,我也很困惑,没有任何问题。很高兴看到您自己发现了这个错误的导入。
    • @xiaoyaoworm - 你的回复迫使我检查我的代码几次。我欠你一杯啤酒。 ;) 最后 - 请看看我的更新。
    • 现在你不欠我一杯啤酒了。您的更新迫使我检查有关此 SerializedName 注释的更多信息。现在我知道 SerializedName 可以使模型中的名称不同。但是,在您的示例中, created_at 和 updated_at 不在 json 响应中,因此您必须使用此注释。我说的对吗?
    • 因为我使用您的 json 响应在我的本地服务器上生成示例,我认为我缺少这两个参数。现在我明白了,呵呵,好问题能让大家学到很多东西。也谢谢你。
    • 是的。我的 JSON 也包含这些字段(我没有提及它们,因为我认为它们与错误无关 - 最后是真的)。谢谢。
    猜你喜欢
    • 2015-12-05
    • 1970-01-01
    • 1970-01-01
    • 2015-06-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多