Как я могу преобразовать эти 2 ответов динамически соответствующего типа?
Это зависит от того, как интерпретировать «соответствующий тип» здесь, потому что это приведет к instanceof
или шаблону для посетителей, чтобы получить соответствующий тип, как только вы пытаетесь справиться разобранной-с-JSON объекта каждый раз, когда вам это нужно. Если вы не можете изменить API, вы можете сгладить то, как вы его используете. Один из возможных вариантов здесь - обработка такого ответа, как будто все это список. Даже один объект может обрабатываться как список только с одним элементом (и многие библиотеки работают с последовательностями/списками, имеющими этот факт: Stream API в Java, LINQ в .NET, jQuery в JavaScript и т. Д.).
Предположим, у вас есть следующий 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=[]
-----------------------------------