不要总是相信自动按模式生成器,因为它们无法为非平凡的情况生成强大而高效的映射。因此,这里非常流行的建议在线工具也无法生成良好的映射,至少是因为:
- 最顶层的 JSON 对象看起来像一个通用的服务器响应,因此可以对其进行参数化。
- JSON 文档中的日期以 Unix 纪元样式表示,因此从该工具的角度来看,它将是
int 或 long。
- 货币名称将被硬编码为字段,因此您将被绑定到 3 种货币,除非您要增强映射,但仍然会失去
Map 的灵活性。
首先,让我们定义映射:
final class Response<T> {
// Note a few things:
// - Gson can deserialize final fields, and these are incoming data supposed to be read-only
// - There are no getters for brevity, these are just incoming data bags anyway
// - If `final`, primitive types like `int` should not be initialized with `0` (javac inlines such constants)
// so we're cheating making them look like constants...
@SerializedName("page") final int page = Integer.valueOf(0);
@SerializedName("pages") final int pages = Integer.valueOf(0);
@SerializedName("rows") final List<T> rows = null;
@SerializedName("total") final int total = Integer.valueOf(0);
}
与之前的类不同,Rate 类不能使用 Gson 通常处于最佳状态的反射策略进行反序列化。该类有一个构造函数,因此我们可以自己实例化它:
final class Rate {
final boolean context;
final Date created;
final Map<String, Currency> currencies;
Rate(final boolean context, final Date created, final Map<String, Currency> currencies) {
this.context = context;
this.created = created;
this.currencies = currencies;
}
}
final class Currency {
@SerializedName("currency") final String currency = null;
@SerializedName("created") final Date created = null;
@SerializedName("rate_buy") final double rateBuy = Double.valueOf(0);
@SerializedName("rate_sell") final double rateSell = Double.valueOf(0);
@SerializedName("context") final boolean context = Boolean.valueOf(false);
}
接下来,需要两个反序列化器:用于日期和费率。
final class DateJsonDeserializer
implements JsonDeserializer<Date> {
// No state? So it can be a singleton
private static final JsonDeserializer<Date> dateJsonDeserializer = new DateJsonDeserializer();
private DateJsonDeserializer() {
}
// But not letting client code know if it's a singleton or not -- it's encapsulated
static JsonDeserializer<Date> getDateJsonDeserializer() {
return dateJsonDeserializer;
}
@Override
public Date deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context)
throws JsonParseException {
// jsonElement.getAsLong() would work either
// But even longs can have their own deserialization strategy
// Note `* 1000` -- `java.util.Date` accepts milliseconds while Unix epoch is "seconds-based"
return new Date((Long) context.deserialize(jsonElement, long.class) * 1000);
}
}
final class RateJsonDeserializer
implements JsonDeserializer<Rate> {
private static final JsonDeserializer<Rate> rateJsonDeserializer = new RateJsonDeserializer();
private RateJsonDeserializer() {
}
static JsonDeserializer<Rate> getRateJsonDeserializer() {
return rateJsonDeserializer;
}
@Override
public Rate deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context)
throws JsonParseException {
final JsonObject jsonObject = jsonElement.getAsJsonObject();
boolean rateContext = false;
Date rateCreated = null;
final Map<String, Currency> rateCurrencies = new LinkedHashMap<>();
// Analyzing rate JSON documents for each key/value pair
for ( final Entry<String, JsonElement> entry : jsonObject.entrySet() ) {
final String key = entry.getKey();
final JsonElement value = entry.getValue();
switch ( key ) {
case "context":
rateContext = context.deserialize(value, boolean.class);
break;
case "created":
rateCreated = context.deserialize(value, Date.class);
break;
default:
rateCurrencies.put(key, context.deserialize(value, Currency.class));
break;
}
}
// So that we can "unwrap" its layout and "flatten" the currencies map
return new Rate(rateContext, rateCreated, rateCurrencies);
}
}
现在,它的使用方式:
private static final TypeToken<Response<Rate>> currencyRatesResponseTypeToken = new TypeToken<Response<Rate>>() {
};
private static final Gson gson = new GsonBuilder()
.registerTypeAdapter(Date.class, getDateJsonDeserializer())
.registerTypeAdapter(Rate.class, getRateJsonDeserializer())
.create();
public static void main(final String... args) {
final Response<Rate> response = gson.fromJson(JSON, currencyRatesResponseTypeToken.getType());
System.out.println(response.page + "/" + response.pages + " (" + response.total + ")");
for ( final Rate rate : response.rows ) {
System.out.println(rate.created);
for ( final Entry<String, Currency> entry : rate.currencies.entrySet() ) {
final Currency currency = entry.getValue();
System.out.println("* " + entry.getKey() + " <== " + currency.currency + " (" + currency.rateBuy + "/" + currency.rateSell + ") " + currency.created);
}
}
}
演示输出:
1/1 (132)
2017 年 3 月 29 日星期三 13:17:41 EEST
* 卢布
* 欧元
* 美元
2017 年 3 月 29 日星期三 12:19:21 EEST
* 卢布
* 欧元
* 美元
2017 年 3 月 29 日星期三 10:26:45 EEST
* 卢布
* 欧元
* 美元