我怎麼能動態轉換爲相應類型的2層的反應?
這取決於如何解釋「適當的類型」在這裏,因爲它會導致instanceof
或訪客模式,以獲得相應的類型,一旦你嘗試解析 - 從 - JSON對象,你需要它,每次處理。如果您無法更改API,則可以平滑使用它的方式。其中一種可能的選擇是處理這種響應,就好像一切都是列表。即使只有一個對象可以作爲僅包含一個元素的列表進行處理(許多庫僅適用於具有以下事實的序列/列表:Java中的Stream API,.NET中的LINQ,JavaScript中的jQuery等)。
假設你有以下MyEntity
類來處理,你需要從API獲得的元素:
// For the testing purposes, package-visible final fields are perfect
// Gson can deal with final fields too
final class MyEntity {
final int id = Integer.valueOf(0); // not letting javac to inline 0 since it's primitive
final String name = null;
@Override
public String toString() {
return id + "=>" + name;
}
}
接下來,讓我們創建一個類型的適配器,將始終一致「真」列表和單個對象,就好像它是一個列表:
final class AlwaysListTypeAdapter<T>
extends TypeAdapter<List<T>> {
private final TypeAdapter<T> elementTypeAdapter;
private AlwaysListTypeAdapter(final TypeAdapter<T> elementTypeAdapter) {
this.elementTypeAdapter = elementTypeAdapter;
}
static <T> TypeAdapter<List<T>> getAlwaysListTypeAdapter(final TypeAdapter<T> elementTypeAdapter) {
return new AlwaysListTypeAdapter<>(elementTypeAdapter);
}
@Override
@SuppressWarnings("resource")
public void write(final JsonWriter out, final List<T> list)
throws IOException {
if (list == null) {
out.nullValue();
} else {
switch (list.size()) {
case 0:
out.beginArray();
out.endArray();
break;
case 1:
elementTypeAdapter.write(out, list.iterator().next());
break;
default:
out.beginArray();
for (final T element : list) {
elementTypeAdapter.write(out, element);
}
out.endArray();
break;
}
}
}
@Override
public List<T> read(final JsonReader in)
throws IOException {
final JsonToken token = in.peek();
switch (token) {
case BEGIN_ARRAY:
final List<T> list = new ArrayList<>();
in.beginArray();
while (in.peek() != END_ARRAY) {
list.add(elementTypeAdapter.read(in));
}
in.endArray();
return unmodifiableList(list);
case BEGIN_OBJECT:
return singletonList(elementTypeAdapter.read(in));
case NULL:
return null;
case END_ARRAY:
case END_OBJECT:
case NAME:
case STRING:
case NUMBER:
case BOOLEAN:
case END_DOCUMENT:
throw new MalformedJsonException("Unexpected token: " + token);
default:
// A guard case: what if Gson would add another token someday?
throw new AssertionError("Must never happen: " + token);
}
}
}
GSON TypeAdapter
設計以流方式工作因此它們從效率的角度便宜,但實施並不容易。上面的write()
方法僅僅是爲了不把throw new UnsupportedOperationException();
放在那裏(我假設你只是閱讀那個API,但不知道這個API是否可能使用「元素或列表」修改請求)。現在有必要建立一個類型的適配器工廠讓GSON拿起每一個特定類型的正確類型的適配器:
final class AlwaysListTypeAdapterFactory
implements TypeAdapterFactory {
private static final TypeAdapterFactory alwaysListTypeAdapterFactory = new AlwaysListTypeAdapterFactory();
private AlwaysListTypeAdapterFactory() {
}
static TypeAdapterFactory getAlwaysListTypeAdapterFactory() {
return alwaysListTypeAdapterFactory;
}
@Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken)
throws IllegalArgumentException {
if (List.class.isAssignableFrom(typeToken.getRawType())) {
final Type elementType = getElementType(typeToken);
// Class<T> instances can be compared with ==
final TypeAdapter<?> elementTypeAdapter = elementType == MyEntity.class ? gson.getAdapter(MyEntity.class) : null;
// Found supported element type adapter?
if (elementTypeAdapter != null) {
@SuppressWarnings("unchecked")
final TypeAdapter<T> castTypeAdapter = (TypeAdapter<T>) getAlwaysListTypeAdapter(elementTypeAdapter);
return castTypeAdapter;
}
}
// Not a type that can be handled? Let Gson pick a more appropriate one itself
return null;
}
// Attempt to detect the list element type
private static Type getElementType(final TypeToken<?> typeToken) {
final Type listType = typeToken.getType();
return listType instanceof ParameterizedType
? ((ParameterizedType) listType).getActualTypeArguments()[0]
: Object.class;
}
}
而且它是如何使用的畢竟:
private static final Type responseItemListType = new TypeToken<List<MyEntity>>() {
}.getType();
private static final Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(getAlwaysListTypeAdapterFactory())
.create();
public static void main(final String... args) {
test("");
test("{\"id\":1,\"name\":\"name\"}");
test("[{\"id\":1,\"name\":\"name\"},{\"id\":1,\"name\":\"name\"}]");
test("[]");
}
private static void test(final String incomingJson) {
final List<MyEntity> list = gson.fromJson(incomingJson, responseItemListType);
System.out.print("LIST=");
System.out.println(list);
System.out.print("JSON=");
gson.toJson(list, responseItemListType, System.out); // no need to create an intermediate string, let it just stream
System.out.println();
System.out.println("-----------------------------------");
}
輸出:
LIST=null
JSON=null
-----------------------------------
LIST=[1=>name]
JSON={"id":1,"name":"name"}
-----------------------------------
LIST=[1=>name, 1=>name]
JSON=[{"id":1,"name":"name"},{"id":1,"name":"name"}]
-----------------------------------
LIST=[]
JSON=[]
-----------------------------------