是否有可能实现以下两个目标?
下面我将介绍两种可能只实现其中一种方法的方法。
我正在使用的API返回成功的类似:
{
"type": "success",
"data": {
"display_name": "Invisible Pink Unicorn",
"user_email": "user@example.com",
"user_id": 1234
}
}
或错误,例如:
{
"type": "error",
"data": {
"error_name": "incorrect_password",
"error_message": "The username or password you entered is incorrect."
}
}
目前处理它的方式是注册一个TypeAdapter,如果类型为"error_message"
,它会抛出给定"error"
的异常:
new GsonBuilder()
.registerTypeAdapter(User.class, new ContentDeserializer<User>())
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.create()
public class ContentDeserializer<T> implements JsonDeserializer<T> {
@Override
public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
final JsonObject object = json.getAsJsonObject();
final String type = object.get("type").getAsString();
final JsonElement data = object.get("data");
final Gson gson = new Gson();
if ("error".equals(type)) {
throw gson.fromJson(data, ApiError.class);
} else {
return gson.fromJson(data, typeOfT);
}
}
}
哪个很整洁,因为它非常简洁,并使用默认的反序列化器来完成所有艰苦的工作。
但实际上它是错误的,因为它没有使用相同的Gson将该工作委托给,所以它将使用不同的字段命名策略,例如。
为了解决这个问题,我写了一个TypeAdapterFactory:
public class UserAdapterFactory implements TypeAdapterFactory {
@SuppressWarnings("unchecked")
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if (!User.class.isAssignableFrom(type.getRawType())) return null;
final TypeAdapter<User> userAdapter = (TypeAdapter<User>) gson.getDelegateAdapter(this, type);
final TypeAdapter<ApiError> apiErrorAdapter = gson.getAdapter(ApiError.class);
return (TypeAdapter<T>) new Adapter(userAdapter, apiErrorAdapter);
}
private static class Adapter extends TypeAdapter<User> {
private final TypeAdapter<User> userAdapter;
private final TypeAdapter<ApiError> apiErrorAdapter;
Adapter(TypeAdapter<User> userAdapter, TypeAdapter<ApiError> apiErrorAdapter) {
this.userAdapter = userAdapter;
this.apiErrorAdapter = apiErrorAdapter;
}
@Override
public void write(JsonWriter out, User value) throws IOException {
}
@Override
public User read(JsonReader in) throws IOException {
User user = null;
String type = null;
in.beginObject();
while (in.hasNext()) {
switch (in.nextName()) {
case "type":
type = in.nextString();
break;
case "data":
if ("error".equals(type)) {
throw apiErrorAdapter.read(in);
} else if ("success".equals(type)) {
user = userAdapter.read(in);
}
break;
}
}
in.endObject();
return user;
}
}
}
这项工作要多得多,但至少让我委托给同一个Gson配置。
这种方法的问题是当JSON对象具有不同的顺序时它会中断:
{
"data": {
"display_name": "Invisible Pink Unicorn",
"user_email": "user@example.com",
"user_id": 1234
},
"type": "success"
}
我没有看到任何方法,因为我不认为JsonReader
有一个选项来读取输入两次,也没有办法将“数据”值缓存在抽象类型中,如遇到“类型”后解析JsonElement
。
答案 0 :(得分:2)
但实际上它是错误的,因为它没有使用相同的Gson将该工作委托给它,所以它将使用不同的字段命名策略,例如。
正确。您应该使用JsonDeserializationContext
。
...因为我不认为JsonReader有两次读取输入的选项,所以也无法在JsonElement之类的抽象类型中缓存“data”值,以便在遇到“type”后进行解析。 / p>
正确。 JsonReader
是一个流阅读器,而JsonElement
是一棵树。这些就像来自XML世界的SAX和DOM一样,各有利弊。流式读取器只读取输入流,您必须自己缓冲/缓存中间数据。
你可以使用这两种方法,但我会选择JsonDeserializer
以简化(假设你不打算写一个超快速解串器)。
我不确定你的User
和ApiError
是如何相互关联的,但我会选择两种不同类型的公共类:真值和错误。看起来你的两个班级有一个共同的父母或祖先,但我不确定你如何在一个电话站点处理它们(也许是instanceof
?)。说,像这样的东西(隐藏构造函数以封装对象结构初始化的复杂性):
final class Content<T> {
private final boolean isSuccess;
private final T data;
private final ApiError error;
private Content(final boolean isSuccess, final T data, final ApiError error) {
this.isSuccess = isSuccess;
this.data = data;
this.error = error;
}
static <T> Content<T> success(final T data) {
return new Content<>(true, data, null);
}
static <T> Content<T> error(final ApiError error) {
return new Content<>(false, null, error);
}
boolean isSuccess() {
return isSuccess;
}
T getData()
throws IllegalStateException {
if ( !isSuccess ) {
throw new IllegalStateException();
}
return data;
}
ApiError getError()
throws IllegalStateException {
if ( isSuccess ) {
throw new IllegalStateException();
}
return error;
}
}
从我的角度来看User
和ApiError
(我更喜欢@SerializedName
但是对命名有更强的控制权 - 但这似乎是习惯问题。)
final class ApiError {
@SuppressWarnings("error_name")
final String errorName = null;
@SerializedName("error_message")
final String errorMessage = null;
}
final class User {
@SerializedName("display_name")
final String displayName = null;
@SerializedName("user_email")
final String userEmail = null;
@SuppressWarnings("user_id")
final int userId = Integer.valueOf(0);
}
接下来,由于树操作更容易,只需实现您的JSON反序列化器:
final class ContentJsonDeserializer<T>
implements JsonDeserializer<Content<T>> {
// This deserializer holds no state
private static final JsonDeserializer<?> contentJsonDeserializer = new ContentJsonDeserializer<>();
private ContentJsonDeserializer() {
}
// ... and we hide away that fact not letting this one to be instantiated at call sites
static <T> JsonDeserializer<T> getContentJsonDeserializer() {
// Narrowing down the @SuppressWarnings scope -- suppressing warnings for entire method may be harmful
@SuppressWarnings("unchecked")
final JsonDeserializer<T> contentJsonDeserializer = (JsonDeserializer<T>) ContentJsonDeserializer.contentJsonDeserializer;
return contentJsonDeserializer;
}
@Override
public Content<T> deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context)
throws JsonParseException {
final JsonObject jsonObject = jsonElement.getAsJsonObject();
final String responseType = jsonObject.getAsJsonPrimitive("type").getAsString();
switch ( responseType ) {
case "success":
return success(context.deserialize(jsonObject.get("data"), getTypeParameter0(type)));
case "error":
return error(context.deserialize(jsonObject.get("data"), ApiError.class));
default:
throw new JsonParseException(responseType);
}
}
// Trying to detect any given type parameterization for its first type parameter
private static Type getTypeParameter0(final Type type) {
if ( !(type instanceof ParameterizedType) ) {
return Object.class;
}
return ((ParameterizedType) type).getActualTypeArguments()[0];
}
}
演示:
private static final Gson gson = new GsonBuilder()
.registerTypeAdapter(Content.class, getContentJsonDeserializer())
.create();
private static final Type userContent = new TypeToken<Content<User>>() {
}.getType();
public static void main(final String... args)
throws IOException {
for ( final String name : ImmutableList.of("success.json", "error.json", "success-reversed.json", "error-reversed.json") ) {
try ( final JsonReader jsonReader = getPackageResourceJsonReader(Q44400163.class, name) ) {
final Content<User> content = gson.fromJson(jsonReader, userContent);
if ( content.isSuccess() ) {
System.out.println("SUCCESS: " + content.getData().displayName);
} else {
System.out.println("ERROR: " + content.getError().errorMessage);
}
}
}
}
输出:
成功:隐形粉红独角兽
错误:您输入的用户名或密码不正确 成功:隐形粉红独角兽
错误:您输入的用户名或密码不正确。
现在,回到原来关于TypeAdapter
的问题。正如我上面提到的,您也可以使用类型适配器来实现,但是您必须实现两种情况支持:
type
属性,然后根据您的实际日期类型阅读data
属性。顺便说一句,您的TypeAdapter
实现远非通用:您必须使用Gson.getDelegateAdapter
解析实际数据类型及其适配器。data
属性读入树视图(因此将其缓冲到内存中)作为JsonElement
实例(您必须从TypeAdapter<JsonElement>
获取Gson
首先是create
方法中的type
实例,然后根据下一个TypeAdapter.fromJsonTree
属性值,使用type
将其作为树中的值读取。是的,不要忘记在这里检查解析状态(以某种方式处理两个案例中缺少data
和{{1}})。如您所见,这会引入可变复杂性和性能/内存成本,但它可以为您提供最佳性能。你决定了。