如何使用 Retrofit2 处理嵌套 JsonObjects 的动态字段
How to handle dynamic fields of nested JsonObjects using Retrofit2
最近在制作一个使用 Rest 的项目时 API 我决定使用 Retrofit。但是,在向 API 发送 GET 请求后,我遇到了一个问题,尽管有人问过类似的问题,但我觉得无法找到解决方案。
我遇到的问题(我相信这就是问题所在)是 API 给出的响应会根据给定的输入而变化,因此我的 POJO 并不总是匹配。更具体地说,我有时会得到一个对象而不是一个列表作为字段。错误的要点在这一行中得到了很好的描述:
Caused by: java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 250 column 20 path $.LocationList.CoordLocation
从 API 收到的 Json 大多数时候看起来像这样:
{
"LocationList": {
"StopLocation": [
{
"name": String,
"x": String,
"y": String,
"id": String
},
...
],
"CoordLocation": [
{
"name": String,
"x": String,
"y": String,
"type": String
},
...
]
}
}
}
一切都很好,因为我的 LocationList POJO 被定义为具有 CoordLocations 和 StopLocations 的列表。但是,当某些输入的 API 仅响应 CoordLocation 的单个 StopLocation 时,它就会崩溃,因为收到的 Json 是单个对象而不是具有单个对象条目的数组:
{
"LocationList": {
"StopLocation": [
{
"name": String,
"x": String,
"y": String,
"id": String
},
...
],
"CoordLocation": {
"name": String,
"x": String,
"y": String,
"type": String
}
}
}
所以我的问题是如何解决这个问题?到目前为止,我已经看到有人建议自定义 TypeAdapters 和反序列化器,但每次我尝试遵循解决方案时,我都会在某个时候碰壁,因为情况略有不同,而且因为我对一切都很陌生 API相关。
在这种情况下,我建议您使用 JsonElement
作为您的回复。当您获得响应时,检查它是 JsonObject
还是 JsonArray
,然后将响应对象转换为 pojo
或其他内容。
所以事实证明自定义 TypeAdapter 是可行的方法,我只是没有足够的经验来编写我自己的前几次尝试。但我终于成功了,结果是这样的:
package com.example.android_navigation.RejseplanenAPI;
import com.example.android_navigation.RejseplanenAPI.POJOs.CoordLocation;
import com.example.android_navigation.RejseplanenAPI.POJOs.Location;
import com.example.android_navigation.RejseplanenAPI.POJOs.StopLocation;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class LocationTypeAdapter extends TypeAdapter<Location> {
@Override
public Location read(JsonReader reader) throws IOException {
Location location = new Location();
Gson gson = new Gson();
JsonParser parser = new JsonParser();
JsonObject jsonObject = (JsonObject)parser.parse(reader);
JsonObject locationListObject = jsonObject.get("LocationList").getAsJsonObject();
JsonElement stopLocations = locationListObject.get("StopLocation");
JsonElement coordLocations = locationListObject.get("CoordLocation");
if(stopLocations != null) {
if(stopLocations.isJsonArray()){
StopLocation[] stopArray = gson.fromJson(stopLocations, StopLocation[].class);
location.getLocationList().setStopLocations(Arrays.asList(stopArray));
}
else if (stopLocations.isJsonObject()) {
StopLocation stopLocation = gson.fromJson(stopLocations, StopLocation.class);
List<StopLocation> stopLocationsList = new ArrayList<>();
stopLocationsList.add(stopLocation);
location.getLocationList().setStopLocations(stopLocationsList);
}
}
if(coordLocations != null) {
if (coordLocations.isJsonArray()) {
CoordLocation[] coordArray = gson.fromJson(coordLocations, CoordLocation[].class);
location.getLocationList().setCoordLocations(Arrays.asList(coordArray));
} else if (coordLocations.isJsonObject()) {
CoordLocation coordLocation = gson.fromJson(coordLocations, CoordLocation.class);
List<CoordLocation> coordLocationsList = new ArrayList<>();
coordLocationsList.add(coordLocation);
location.getLocationList().setCoordLocations(coordLocationsList);
}
}
return location;
}
@Override
public void write(JsonWriter out, Location value) throws IOException {
}
}
然后在构建 Retrofit 实例中使用的 Gson 时添加:
private Gson gson = new GsonBuilder()
.registerTypeAdapter(Location.class, new LocationTypeAdapter())
.setLenient()
.create();
private Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://xmlopen.rejseplanen.dk/bin/rest.exe/")
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
最近在制作一个使用 Rest 的项目时 API 我决定使用 Retrofit。但是,在向 API 发送 GET 请求后,我遇到了一个问题,尽管有人问过类似的问题,但我觉得无法找到解决方案。
我遇到的问题(我相信这就是问题所在)是 API 给出的响应会根据给定的输入而变化,因此我的 POJO 并不总是匹配。更具体地说,我有时会得到一个对象而不是一个列表作为字段。错误的要点在这一行中得到了很好的描述:
Caused by: java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 250 column 20 path $.LocationList.CoordLocation
从 API 收到的 Json 大多数时候看起来像这样:
{
"LocationList": {
"StopLocation": [
{
"name": String,
"x": String,
"y": String,
"id": String
},
...
],
"CoordLocation": [
{
"name": String,
"x": String,
"y": String,
"type": String
},
...
]
}
}
}
一切都很好,因为我的 LocationList POJO 被定义为具有 CoordLocations 和 StopLocations 的列表。但是,当某些输入的 API 仅响应 CoordLocation 的单个 StopLocation 时,它就会崩溃,因为收到的 Json 是单个对象而不是具有单个对象条目的数组:
{
"LocationList": {
"StopLocation": [
{
"name": String,
"x": String,
"y": String,
"id": String
},
...
],
"CoordLocation": {
"name": String,
"x": String,
"y": String,
"type": String
}
}
}
所以我的问题是如何解决这个问题?到目前为止,我已经看到有人建议自定义 TypeAdapters 和反序列化器,但每次我尝试遵循解决方案时,我都会在某个时候碰壁,因为情况略有不同,而且因为我对一切都很陌生 API相关。
在这种情况下,我建议您使用 JsonElement
作为您的回复。当您获得响应时,检查它是 JsonObject
还是 JsonArray
,然后将响应对象转换为 pojo
或其他内容。
所以事实证明自定义 TypeAdapter 是可行的方法,我只是没有足够的经验来编写我自己的前几次尝试。但我终于成功了,结果是这样的:
package com.example.android_navigation.RejseplanenAPI;
import com.example.android_navigation.RejseplanenAPI.POJOs.CoordLocation;
import com.example.android_navigation.RejseplanenAPI.POJOs.Location;
import com.example.android_navigation.RejseplanenAPI.POJOs.StopLocation;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class LocationTypeAdapter extends TypeAdapter<Location> {
@Override
public Location read(JsonReader reader) throws IOException {
Location location = new Location();
Gson gson = new Gson();
JsonParser parser = new JsonParser();
JsonObject jsonObject = (JsonObject)parser.parse(reader);
JsonObject locationListObject = jsonObject.get("LocationList").getAsJsonObject();
JsonElement stopLocations = locationListObject.get("StopLocation");
JsonElement coordLocations = locationListObject.get("CoordLocation");
if(stopLocations != null) {
if(stopLocations.isJsonArray()){
StopLocation[] stopArray = gson.fromJson(stopLocations, StopLocation[].class);
location.getLocationList().setStopLocations(Arrays.asList(stopArray));
}
else if (stopLocations.isJsonObject()) {
StopLocation stopLocation = gson.fromJson(stopLocations, StopLocation.class);
List<StopLocation> stopLocationsList = new ArrayList<>();
stopLocationsList.add(stopLocation);
location.getLocationList().setStopLocations(stopLocationsList);
}
}
if(coordLocations != null) {
if (coordLocations.isJsonArray()) {
CoordLocation[] coordArray = gson.fromJson(coordLocations, CoordLocation[].class);
location.getLocationList().setCoordLocations(Arrays.asList(coordArray));
} else if (coordLocations.isJsonObject()) {
CoordLocation coordLocation = gson.fromJson(coordLocations, CoordLocation.class);
List<CoordLocation> coordLocationsList = new ArrayList<>();
coordLocationsList.add(coordLocation);
location.getLocationList().setCoordLocations(coordLocationsList);
}
}
return location;
}
@Override
public void write(JsonWriter out, Location value) throws IOException {
}
}
然后在构建 Retrofit 实例中使用的 Gson 时添加:
private Gson gson = new GsonBuilder()
.registerTypeAdapter(Location.class, new LocationTypeAdapter())
.setLenient()
.create();
private Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://xmlopen.rejseplanen.dk/bin/rest.exe/")
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();