如何解析内部元素无键的嵌套List JSON?Java对象结构配置
解决JSON嵌套数组的Gson解析问题
问题分析
你遇到两个核心问题:
- 根节点解析错误:报错
Expected BEGIN_ARRAY but was BEGIN_OBJECT,说明Retrofit接口的返回类型定义错误——API实际返回单个JSON对象,但你可能定义成了Call<List<QueryResult>>或Observable<List<QueryResult>>。 - 嵌套数组映射错误:API返回的
prices和market_caps是无键数组的数组,而非对象数组,你定义的List<List<Prices>>无法让Gson自动将无键数组映射到带字段的Prices类。
正确的解决方案
步骤1:修正Retrofit接口的返回类型
确保接口返回单个QueryResult对象,而非列表。示例:
public interface PriceApi { @GET("your-target-endpoint") Observable<QueryResult> getPriceData(); // 若不用RxJava,可改为:Call<QueryResult> getPriceData(); }
步骤2:调整实体类结构并添加自定义TypeAdapter
由于JSON中的每个价格/市值条目是无键数组([时间戳, 数值]),需要自定义Gson的TypeAdapter来完成数组到实体类的映射:
1. 定义基础实体类
public class PriceEntry { private long timestamp; private double price; public PriceEntry(long timestamp, double price) { this.timestamp = timestamp; this.price = price; } // Getters public long getTimestamp() { return timestamp; } public double getPrice() { return price; } } public class MarketCapEntry { private long timestamp; private double marketCap; public MarketCapEntry(long timestamp, double marketCap) { this.timestamp = timestamp; this.marketCap = marketCap; } // Getters public long getTimestamp() { return timestamp; } public double getMarketCap() { return marketCap; } }
2. 编写自定义TypeAdapter
为PriceEntry和MarketCapEntry实现TypeAdapter,处理无键数组的解析:
public class PriceEntryTypeAdapter extends TypeAdapter<PriceEntry> { @Override public void write(JsonWriter out, PriceEntry value) throws IOException { // 若无需序列化可留空,或按需求实现序列化逻辑 out.beginArray(); out.value(value.getTimestamp()); out.value(value.getPrice()); out.endArray(); } @Override public PriceEntry read(JsonReader in) throws IOException { in.beginArray(); long timestamp = in.nextLong(); double price = in.nextDouble(); in.endArray(); return new PriceEntry(timestamp, price); } } public class MarketCapEntryTypeAdapter extends TypeAdapter<MarketCapEntry> { @Override public void write(JsonWriter out, MarketCapEntry value) throws IOException { out.beginArray(); out.value(value.getTimestamp()); out.value(value.getMarketCap()); out.endArray(); } @Override public MarketCapEntry read(JsonReader in) throws IOException { in.beginArray(); long timestamp = in.nextLong(); double marketCap = in.nextDouble(); in.endArray(); return new MarketCapEntry(timestamp, marketCap); } }
3. 调整QueryResult类并关联TypeAdapter
public class QueryResult { @SerializedName("prices") @JsonAdapter(PriceEntryTypeAdapter.class) private List<PriceEntry> prices; @SerializedName("market_caps") @JsonAdapter(MarketCapEntryTypeAdapter.class) private List<MarketCapEntry> marketCaps; // Getters public List<PriceEntry> getPrices() { return prices; } public List<MarketCapEntry> getMarketCaps() { return marketCaps; } }
步骤3:全局注册TypeAdapter(可选)
如果不想在实体类上使用@JsonAdapter注解,可以在创建Gson实例时全局注册TypeAdapter:
@Provides @Singleton public Retrofit provideRetrofit(OkHttpClient okHttpClient) { Gson gson = new GsonBuilder() .registerTypeAdapter(PriceEntry.class, new PriceEntryTypeAdapter()) .registerTypeAdapter(MarketCapEntry.class, new MarketCapEntryTypeAdapter()) .create(); return new Retrofit.Builder() .baseUrl(Constants.BASE_URL) .addConverterFactory(GsonConverterFactory.create(gson)) .addCallAdapterFactory(RxJava3CallAdapterFactory.create()) .client(okHttpClient) .build(); }
简化方案(无需自定义TypeAdapter)
如果不需要实体类,可直接用List<List<Double>>接收数据,使用时手动转换类型:
public class QueryResult { @SerializedName("prices") private List<List<Double>> prices; @SerializedName("market_caps") private List<List<Double>> marketCaps; // Getters public List<List<Double>> getPrices() { return prices; } public List<List<Double>> getMarketCaps() { return marketCaps; } }
转换示例:
long timestamp = prices.get(0).get(0).longValue(); double price = prices.get(0).get(1);
内容的提问来源于stack exchange,提问作者WHOATEMYNOODLES
相关产品推荐
相关产品推荐

