使用包含变量类型字段的 Gson 反序列化 JSON 响应
Deserialize a JSON response with Gson containing a field of variable type
REST API 的响应总是 return 具有以下结构的 JSON:
{
"status": "<status_code>",
"data": <data_object>
}
我的问题是 data
的值没有唯一类型,但它可以是字符串、JSON 对象或 JSON 数组,具体取决于被调用端点。我不知道如何以正确的方式反序列化它以创建不同的 Java 对象...
比如我已经准备了一些POJO:根元素
public class ApiResult {
@SerializedName("status")
public String status;
@SerializedName("data")
public JsonElement data; // should I define it as a JsonElement??
}
和反映两个端点的两个对象:
// "data" can be a list of NavItems
public class NavItem {
@SerializedName("id")
public String id;
@SerializedName("name")
public String name;
@SerializedName("icon")
public String icon;
@SuppressWarnings("serial")
public static class List extends ArrayList<NavItem> {}
}
和
// "data" can be a single object representing a Profile
public class Profile {
@SerializedName("id")
public String id;
@SerializedName("fullname")
public String fullname;
@SerializedName("avatar")
public String avatar;
}
阅读一些 Whosebug 问题后,我发现我应该使用 JsonDeserializer<T>
界面。但是如果 ApiResult
中 data
的类型是可变的呢?
你应该使用自定义 JsonDeserializer
并在那里写下你所有的逻辑,就像这样
ApiResult.java
public class ApiResult {
@SerializedName("status")
public String status;
@SerializedName("data")
public Object data;
}
ApiResultDeserializer.java
import java.lang.reflect.Type;
import java.util.List;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.reflect.TypeToken;
public class ApiResultDeserializer implements JsonDeserializer<ApiResult> {
private Type listType = new TypeToken<List<NavItem>>(){}.getType();
@Override
public ApiResult deserialize(JsonElement value, Type type,
JsonDeserializationContext context) throws JsonParseException {
final JsonObject apiResultJson = value.getAsJsonObject();
final ApiResult result = new ApiResult();
result.status = apiResultJson.get("status").getAsString();
JsonElement dataJson = apiResultJson.get("data");
if(dataJson.isJsonObject()) {
result.data = context.deserialize(dataJson, NavItem.class);
} else if(dataJson.isJsonPrimitive()) {
result.data = context.deserialize(dataJson, String.class);
} else if(dataJson.isJsonArray()) {
result.data = context.deserialize(dataJson, listType);
}
return result;
}
}
并尝试创建不同种类的 data
(List
、Object
或 String
),如您所述
Main.java
Gson gson = new GsonBuilder()
.registerTypeAdapter(ApiResult.class, new ApiResultDeserializer())
.create();
List<NavItem> navItems = new ArrayList<NavItem>();
for(int i = 1 ; i < 6 ; ++i) {
navItems.add(new NavItem(i+"", "Name-" + i, "Icon-" + i ));
}
ApiResult result = new ApiResult();
result.status = "OK";
result.data = navItems;
// Serialization
System.out.println(gson.toJson(result)); // {\"status\":\"OK\",\"data\":[{\"id\":\"1\",\"name\":\"Name-1\",\"icon\":\"Icon-1\"},{\"id\":\"2\",\"name\":\"Name-2\",\"icon\":\"Icon-2\"},{\"id\":\"3\",\"name\":\"Name-3\",\"icon\":\"Icon-3\"},{\"id\":\"4\",\"name\":\"Name-4\",\"icon\":\"Icon-4\"},{\"id\":\"5\",\"name\":\"Name-5\",\"icon\":\"Icon-5\"}]}
result.data = navItems.get(0);
System.out.println(gson.toJson(result)); // {\"status\":\"OK\",\"data\":{\"id\":\"1\",\"name\":\"Name-1\",\"icon\":\"Icon-1\"}}
result.data = "Test";
System.out.println(gson.toJson(result)); // {\"status\":\"OK\",\"data\":\"Test\"}
// Deserialization
String input = "{\"status\":\"OK\",\"data\":[{\"id\":\"1\",\"name\":\"Name-1\",\"icon\":\"Icon-1\"},{\"id\":\"2\",\"name\":\"Name-2\",\"icon\":\"Icon-2\"},{\"id\":\"3\",\"name\":\"Name-3\",\"icon\":\"Icon-3\"},{\"id\":\"4\",\"name\":\"Name-4\",\"icon\":\"Icon-4\"},{\"id\":\"5\",\"name\":\"Name-5\",\"icon\":\"Icon-5\"}]}";
ApiResult newResult = gson.fromJson(input, ApiResult.class);
System.out.println(newResult.data); // Array
input = "{\"status\":\"OK\",\"data\":{\"id\":\"1\",\"name\":\"Name-1\",\"icon\":\"Icon-1\"}}";
newResult = gson.fromJson(input, ApiResult.class);
System.out.println(newResult.data); // Object
input = "{\"status\":\"OK\",\"data\":\"Test\"}";
newResult = gson.fromJson(input, ApiResult.class);
System.out.println(newResult.data); // String
我设法让它按我想要的方式工作,而且没有使用任何自定义反序列化器!
对于每个端点,我等待响应(顺便说一句,我正在使用 Volley),然后我首先生成 "root" ApiResult 对象,检查状态是否正常,然后我继续将数据字段实例化为请求的类型。
POJO 与问题相同。在ApiResult
中,"data"是一个JsonElement
。
// ... called the endpoint that returns a NavItem list
public void onResponse(String response) {
ApiResult rootResult = gson.fromJson(response.toString(), ApiResult.class);
if (rootResult.status.equals(STATUS_OK)) {
Log.d(LOG_TAG, response.toString());
NavItem.List resData = gson.fromJson(rootResult.data, NavItem.List.class); // <-- !!!!!
callback.onSuccess(resData);
}
else {
Log.e(LOG_TAG, response.toString());
callback.onError(-1, null);
}
}
显然,"Profile" 端点唯一需要更改的是带有 !!!!!
的行
REST API 的响应总是 return 具有以下结构的 JSON:
{
"status": "<status_code>",
"data": <data_object>
}
我的问题是 data
的值没有唯一类型,但它可以是字符串、JSON 对象或 JSON 数组,具体取决于被调用端点。我不知道如何以正确的方式反序列化它以创建不同的 Java 对象...
比如我已经准备了一些POJO:根元素
public class ApiResult {
@SerializedName("status")
public String status;
@SerializedName("data")
public JsonElement data; // should I define it as a JsonElement??
}
和反映两个端点的两个对象:
// "data" can be a list of NavItems
public class NavItem {
@SerializedName("id")
public String id;
@SerializedName("name")
public String name;
@SerializedName("icon")
public String icon;
@SuppressWarnings("serial")
public static class List extends ArrayList<NavItem> {}
}
和
// "data" can be a single object representing a Profile
public class Profile {
@SerializedName("id")
public String id;
@SerializedName("fullname")
public String fullname;
@SerializedName("avatar")
public String avatar;
}
阅读一些 Whosebug 问题后,我发现我应该使用 JsonDeserializer<T>
界面。但是如果 ApiResult
中 data
的类型是可变的呢?
你应该使用自定义 JsonDeserializer
并在那里写下你所有的逻辑,就像这样
ApiResult.java
public class ApiResult {
@SerializedName("status")
public String status;
@SerializedName("data")
public Object data;
}
ApiResultDeserializer.java
import java.lang.reflect.Type;
import java.util.List;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.reflect.TypeToken;
public class ApiResultDeserializer implements JsonDeserializer<ApiResult> {
private Type listType = new TypeToken<List<NavItem>>(){}.getType();
@Override
public ApiResult deserialize(JsonElement value, Type type,
JsonDeserializationContext context) throws JsonParseException {
final JsonObject apiResultJson = value.getAsJsonObject();
final ApiResult result = new ApiResult();
result.status = apiResultJson.get("status").getAsString();
JsonElement dataJson = apiResultJson.get("data");
if(dataJson.isJsonObject()) {
result.data = context.deserialize(dataJson, NavItem.class);
} else if(dataJson.isJsonPrimitive()) {
result.data = context.deserialize(dataJson, String.class);
} else if(dataJson.isJsonArray()) {
result.data = context.deserialize(dataJson, listType);
}
return result;
}
}
并尝试创建不同种类的 data
(List
、Object
或 String
),如您所述
Main.java
Gson gson = new GsonBuilder()
.registerTypeAdapter(ApiResult.class, new ApiResultDeserializer())
.create();
List<NavItem> navItems = new ArrayList<NavItem>();
for(int i = 1 ; i < 6 ; ++i) {
navItems.add(new NavItem(i+"", "Name-" + i, "Icon-" + i ));
}
ApiResult result = new ApiResult();
result.status = "OK";
result.data = navItems;
// Serialization
System.out.println(gson.toJson(result)); // {\"status\":\"OK\",\"data\":[{\"id\":\"1\",\"name\":\"Name-1\",\"icon\":\"Icon-1\"},{\"id\":\"2\",\"name\":\"Name-2\",\"icon\":\"Icon-2\"},{\"id\":\"3\",\"name\":\"Name-3\",\"icon\":\"Icon-3\"},{\"id\":\"4\",\"name\":\"Name-4\",\"icon\":\"Icon-4\"},{\"id\":\"5\",\"name\":\"Name-5\",\"icon\":\"Icon-5\"}]}
result.data = navItems.get(0);
System.out.println(gson.toJson(result)); // {\"status\":\"OK\",\"data\":{\"id\":\"1\",\"name\":\"Name-1\",\"icon\":\"Icon-1\"}}
result.data = "Test";
System.out.println(gson.toJson(result)); // {\"status\":\"OK\",\"data\":\"Test\"}
// Deserialization
String input = "{\"status\":\"OK\",\"data\":[{\"id\":\"1\",\"name\":\"Name-1\",\"icon\":\"Icon-1\"},{\"id\":\"2\",\"name\":\"Name-2\",\"icon\":\"Icon-2\"},{\"id\":\"3\",\"name\":\"Name-3\",\"icon\":\"Icon-3\"},{\"id\":\"4\",\"name\":\"Name-4\",\"icon\":\"Icon-4\"},{\"id\":\"5\",\"name\":\"Name-5\",\"icon\":\"Icon-5\"}]}";
ApiResult newResult = gson.fromJson(input, ApiResult.class);
System.out.println(newResult.data); // Array
input = "{\"status\":\"OK\",\"data\":{\"id\":\"1\",\"name\":\"Name-1\",\"icon\":\"Icon-1\"}}";
newResult = gson.fromJson(input, ApiResult.class);
System.out.println(newResult.data); // Object
input = "{\"status\":\"OK\",\"data\":\"Test\"}";
newResult = gson.fromJson(input, ApiResult.class);
System.out.println(newResult.data); // String
我设法让它按我想要的方式工作,而且没有使用任何自定义反序列化器!
对于每个端点,我等待响应(顺便说一句,我正在使用 Volley),然后我首先生成 "root" ApiResult 对象,检查状态是否正常,然后我继续将数据字段实例化为请求的类型。
POJO 与问题相同。在ApiResult
中,"data"是一个JsonElement
。
// ... called the endpoint that returns a NavItem list
public void onResponse(String response) {
ApiResult rootResult = gson.fromJson(response.toString(), ApiResult.class);
if (rootResult.status.equals(STATUS_OK)) {
Log.d(LOG_TAG, response.toString());
NavItem.List resData = gson.fromJson(rootResult.data, NavItem.List.class); // <-- !!!!!
callback.onSuccess(resData);
}
else {
Log.e(LOG_TAG, response.toString());
callback.onError(-1, null);
}
}
显然,"Profile" 端点唯一需要更改的是带有 !!!!!