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<Country> 映射。作为第一步,它将抽象JsonElement“转换”为JsonObject,以便遍历其属性("USA"、"Turkey" 和"Lebanon")。假设属性名称本身就是国家名称,城市名称列表(实际上是属性值)可以更深入地委托给序列化上下文并解析为List<String> 实例(注意类型标记)。一旦name 和cities 都被解析,您就可以构造一个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<Country> 的自定义类型和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:
美国 => [纽约、德克萨斯、夏威夷]
土耳其 => [安卡拉,伊斯坦布尔]
黎巴嫩 => [贝鲁特,扎赫勒]