【问题标题】:Retrofit and GSON to parse an Array of objects with no names改造和 GSON 来解析没有名称的对象数组
【发布时间】:2017-01-17 13:59:16
【问题描述】:

我收到这样的 JSON 响应:

{
    "USA": [
        "New York",
        "Texas",
        "Hawaii"
    ],
    "Turkey": [
        "Ankara",
        "Istanbul"
    ],
    "Lebanon": [
        "Beirut",
        "Zahle"
    ]
}

我想得到这个

public class Country {
    private String name = null;
    private List<String> cities = null;
}

如果 JSON 对象没有类似的名称,我们如何解析它们

{
    "name": "USA",
    "cities": [
              "New York",
              .... etc
}

?提前谢谢你。

【问题讨论】:

  • 如果您有访问权限,您可能需要在发送之前在服务器端编辑响应。
  • @NongthonbamTonthoi 我无权访问它:(
  • MBH 改造据我所知是不可能的,但无论如何你可以通过手动复制到另一个对象来实现你想要的。

标签: android gson retrofit retrofit2


【解决方案1】:

对我来说就像一张地图。尝试将其解析为Map&lt;String, List&lt;String&gt;&gt;。然后您可以分别访问键和值。

【讨论】:

  • 有两种方法。简单但效率较低,稍微复杂一点,也许效率更高。 1. 获取地图后,您可以遍历键并创建 Country 对象。 2.您可以实现自定义JSON反序列化器进行改造。您将收到一个原始 JsonObject,您可以编写逻辑将其转换为 Country 对象。选择哪种方式取决于您。
  • 我可以只为这个请求编写转换器吗?因为这个api还有另一个功能很好用
  • 是的,我建议检查 gson.registerTypeAdapter 方法。 Gson 有很好的记录 sites.google.com/site/gson/gson-user-guide
【解决方案2】:

Gson 的默认 DTO 字段注释用于简单的情况。对于更复杂的反序列化,您可能需要使用自定义类型适配器和(反)序列化程序,以避免以更 Gson 惯用的方式来避免弱类型 DTO,例如地图和列表。

假设您有以下 DTO:

final class Country {

    private final String name;
    private final List<String> cities;

    Country(final String name, final List<String> cities) {
        this.name = name;
        this.cities = cities;
    }

    String getName() {
        return name;
    }

    List<String> getCities() {
        return cities;
    }

}

假设一个“非标准”的 JSON 布局,下面的反序列化器将递归地遍历 JSON 对象树以收集目标国家列表。说,

final class CountriesJsonDeserializer
        implements JsonDeserializer<List<Country>> {

    private static final JsonDeserializer<List<Country>> countryArrayJsonDeserializer = new CountriesJsonDeserializer();

    private static final Type listOfStringType = new TypeToken<List<String>>() {
    }.getType();

    private CountriesJsonDeserializer() {
    }

    static JsonDeserializer<List<Country>> getCountryArrayJsonDeserializer() {
        return countryArrayJsonDeserializer;
    }

    @Override
    public List<Country> deserialize(final JsonElement json, final Type type, final JsonDeserializationContext context)
            throws JsonParseException {
        final List<Country> countries = new ArrayList<>();
        final JsonObject root = json.getAsJsonObject();
        for ( final Entry<String, JsonElement> e : root.entrySet() ) {
            final String name = e.getKey();
            final List<String> cities = context.deserialize(e.getValue(), listOfStringType);
            countries.add(new Country(name, cities));
        }
        return countries;
    }

}

上面的反序列化器将绑定到List&lt;Country&gt; 映射。作为第一步,它将抽象JsonElement“转换”为JsonObject,以便遍历其属性("USA""Turkey""Lebanon")。假设属性名称本身就是国家名称,城市名称列表(实际上是属性值)可以更深入地委托给序列化上下文并解析为List&lt;String&gt; 实例(注意类型标记)。一旦namecities 都被解析,您就可以构造一个Country 实例并收集结果列表。

如何使用:

private static final Type listOfCountryType = new TypeToken<List<Country>>() {
}.getType();

private static final Gson gson = new GsonBuilder()
        .registerTypeAdapter(listOfCountryType, getCountryArrayJsonDeserializer())
        .create();

public static void main(final String... args) {
    final List<Country> countries = gson.fromJson(JSON, listOfCountryType);
    for ( final Country country : countries ) {
        out.print(country.getName());
        out.print(" => ");
        out.println(country.getCities());
    }
}

已知类型标记和 Gson 实例是线程安全的,因此它们可以安全地存储为最终静态实例。注意List&lt;Country&gt; 的自定义类型和CountriesJsonDeserializer 的自定义反序列化器是如何相互绑定的。一旦反序列化完成,它将输出:

美国 => [纽约、德克萨斯、夏威夷]
土耳其 => [安卡拉,伊斯坦布尔]
黎巴嫩 => [贝鲁特,扎赫勒]


更新

由于我从未使用过 Retrofit,因此我尝试了以下代码与此配置:

  • com.google.code.gson:gson:2.8.0
  • com.squareup.retrofit2:retrofit:2.1.0
  • com.squareup.retrofit2:converter-gson:2.1.0

定义“geo”服务接口:

interface IGeoService {

    @GET("/countries")
    Call<List<Country>> getCountries();

}

并使用自定义 Gson 感知转换器构建 Retrofit 实例:

// The statics are just borrowed from the example above

public static void main(final String... args) {
    // Build the Retrofit instance
    final Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(... your URL goes here...)
            .addConverterFactory(GsonConverterFactory.create(gson))
            .build();
    // Proxify the geo service by Retrofit
    final IGeoService geoService = retrofit.create(IGeoService.class);
    // Make a call to the remote service
    final Call<List<Country>> countriesCall = geoService.getCountries();
    countriesCall.enqueue(new Callback<List<Country>>() {
        @Override
        public void onResponse(final Call<List<Country>> call, final Response<List<Country>> response) {
            dumpCountries("From a remote JSON:", response.body());
        }

        @Override
        public void onFailure(final Call<List<Country>> call, final Throwable throwable) {
            throw new RuntimeException(throwable);
        }
    });
    // Or just take a pre-defined string
    dumpCountries("From a ready-to-use JSON:", gson.<List<Country>>fromJson(JSON, listOfCountryType));
}

private static void dumpCountries(final String name, final Iterable<Country> countries) {
    out.println(name);
    for ( final Country country : countries ) {
        out.print(country.getName());
        out.print(" => ");
        out.println(country.getCities());
    }
    out.println();
}

如果您因为 Country 类及其 JSON 反序列化器而出现类型冲突(我的意思是,您已经有另一个用于不同目的的“国家”类),只需重命名 this @ 987654343@ 类,以免影响“正常工作”的映射。

输出:

来自现成的 JSON:
美国 => [纽约、德克萨斯、夏威夷]
土耳其 => [安卡拉,伊斯坦布尔]
黎巴嫩 => [贝鲁特,扎赫勒]

来自远程 JSON:
美国 => [纽约、德克萨斯、夏威夷]
土耳其 => [安卡拉,伊斯坦布尔]
黎巴嫩 => [贝鲁特,扎赫勒]

【讨论】:

  • 感谢您提供非常好的答案,这是最接近解决方案的方法,想法是如何将其附加到 Retrofit api 调用?
  • @MBH 我已经更新了我的答案,但是,老实说,这正是我使用 Retrofit 的时候,所以我可能让它不是 Retrofit 惯用的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-29
  • 2021-09-30
相关资源
最近更新 更多