Дети данных узлов всегда кажутся массивами JSON, поэтому первое, что вы могли с ними сделать, - объявить детям как List<?>
, скрывающим фактический тип. Однако у вас все еще есть свойство/поле type
, которое идеально подходит для получения фактического типа детей. Самый простой способ - это просто добавить еще один десериализатор JSON для десериализации Data
экземпляров с некоторыми издержками производительности (поскольку они не являются адаптерами типа), и, насколько мне известно, отсутствует @SerializedName
в полях класса Data
.
Если вы также хорошо с изменив типы DTOS, предпочитаете перечисления, а не сырые нитей, как они работают просто отлично с перечислениями (особенно в сотрудничестве с интеллектуальной Идой):
enum Type {
@SerializedName("node")
NODE,
@SerializedName("extra")
EXTRA
}
Data
самого классом, то может выглядеть следующим образом:
final class Data {
private final Type type;
private final List<?> children; // this one is supposed to be:
// * either List<String> if type=EXTRA
// * or List<Node> if type=NODE
Data(final Type type, final List<?> children) {
this.type = type;
this.children = children;
}
Type getType() {
return type;
}
List<?> getChildren() {
return children;
}
}
extra
поскольку -typed дети просто строки в вашем вопросе, просто добавьте класс узла DTO:
final class Node {
@SerializedName("id")
private final String id = null;
@SerializedName("name")
private final String name = null;
@SerializedName("subdata")
private final Data subdata = null;
String getId() {
return id;
}
String getName() {
return name;
}
Data getSubdata() {
return subdata;
}
}
Теперь, десериализируя класс Data
, вы можете определить фактический тип списка детей и, в соответствии с типом узла, десериализовать его как список строк или список узлов. Обратите внимание, что десериализатор ниже использует java.lang.reflect.Type
экземпляры, а не java.lang.Class
, потому что последний слаб из-за стирания стилей Java-типа и составляет List.class
для любой параметризации списка (строки, узлы и т. Д.). Имея ожидаемые типы, снабженные токенами типа, просто делегируйте пару ключей/значений JSON в контекст десериализации, задающий целевой тип, тем самым делая рекурсивную десериализацию, которая будет работать на уровне произвольных вложенных элементов (однако GSON имеет некоторый внутренний предел стека, который ограничен 32 если я не ошибаюсь).
final class DataJsonDeserializer
implements JsonDeserializer<Data> {
private static final JsonDeserializer<Data> dataJsonDeserializer = new DataJsonDeserializer();
private static final java.lang.reflect.Type nodeListType = new TypeToken<List<Node>>() {
}.getType();
private static final java.lang.reflect.Type stringListType = new TypeToken<List<String>>() {
}.getType();
private DataJsonDeserializer() {
}
static JsonDeserializer<Data> getDataJsonDeserializer() {
return dataJsonDeserializer;
}
@Override
public Data deserialize(final JsonElement jsonElement, final java.lang.reflect.Type type, final JsonDeserializationContext context)
throws JsonParseException {
final JsonObject rootJsonObject = jsonElement.getAsJsonObject();
final Type nodeType = context.deserialize(rootJsonObject.get("type"), Type.class);
final JsonArray childrenJsonArray = rootJsonObject.get("children").getAsJsonArray();
final List<?> children;
switch (nodeType) {
case NODE:
children = context.deserialize(childrenJsonArray, nodeListType);
break;
case EXTRA:
children = context.deserialize(childrenJsonArray, stringListType);
break;
default:
throw new AssertionError(nodeType);
}
return new Data(nodeType, children);
}
}
И демо рекурсивно проходит через детей (обратите внимание на расширенные for
заявления, которые бросают каждый элемент к целевому типу ниже):
public final class EntryPoint {
private static final String JSON_WITH_SUBNODES = "{\"type\":\"node\",\"children\":[{\"id\":\"abc123\",\"name\":\"Name 1\",\"subdata\":{\"type\":\"node\",\"children\":[{\"id\":\"def456\",\"name\":\"Name 2\"}]}}]}";
private static final String JSON_WITH_REFERENCES = "{\"type\":\"node\",\"children\":[{\"id\":\"abc123\",\"name\":\"Name 1\",\"subdata\":{\"type\":\"extra\",\"children\":[\"ghi\",\"jkl\",\"mno\"]}}]}";
private static final Gson gson = new GsonBuilder()
.registerTypeAdapter(Data.class, getDataJsonDeserializer())
.create();
public static void main(final String... args) {
process(gson.fromJson(JSON_WITH_SUBNODES, Data.class));
process(gson.fromJson(JSON_WITH_REFERENCES, Data.class));
}
private static void process(final Data data) {
process(data, 0);
out.println();
}
private static void process(final Data data, final int level) {
for (int i = 0; i < level; i++) {
out.print('>');
}
final List<?> children = data.getChildren();
final Type type = data.getType();
out.println(type);
switch (type) {
case NODE:
@SuppressWarnings("unchecked")
final Iterable<Node> nodeChildren = (Iterable<Node>) children;
for (final Node node : nodeChildren) {
out.printf("\t%s %s\n", node.getId(), node.getName());
final Data subdata = node.getSubdata();
if (subdata != null) {
process(subdata, level + 1);
}
}
break;
case EXTRA:
@SuppressWarnings("unchecked")
final Iterable<String> extraChildren = (Iterable<String>) children;
for (final String extra : extraChildren) {
out.printf("\t%s\n", extra);
}
break;
default:
throw new AssertionError(type);
}
}
}
Выход:
NODE
abc123 Name 1
>NODE
def456 Name 2
NODE
abc123 Name 1
>EXTRA
ghi
jkl
mno
I что-то подобное с Джексоном некоторое время назад: http://stackoverflow.com/a/12459070/823393 – OldCurmudgeon