You have very complex JSON payload where the same property could have one JSON object or JSON array of objects. Gson does not handle this case by default and we need to implement custom deserialiser for this kind of one-or-many properties. Below I created simple POJO model which represents your JSON payload:
class TestResponse {
@SerializedName("test-run")
private TestRun testRun;
// other properties, getters, setters, toString
}
class TestRun {
@SerializedName("test-suite")
private List<TestSuite> testSuite;
// other properties, getters, setters, toString
}
class TestSuite {
private String result;
private double duration;
@SerializedName("test-suite")
private List<TestSuite> testSuites;
@SerializedName("test-case")
private List<TestCase> testCases;
// other properties, getters, setters, toString
}
class TestCase {
private String fullname;
// other properties, getters, setters, toString
}
As you can see test-suite and test-case are List-es properties. Let's implement custom deserialiser for these properties:
class OneOrManyJsonDeserializer<E> implements JsonDeserializer<List<E>> {
private final Class<E> clazz;
public OneOrManyJsonDeserializer(Class<E> clazz) {
this.clazz = clazz;
}
@Override
public List<E> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
if (json instanceof JsonArray) {
final JsonArray array = (JsonArray) json;
final int size = array.size();
if (size == 0) {
return Collections.emptyList();
}
final List<E> suites = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
suites.add(context.deserialize(array.get(i), clazz));
}
return suites;
}
E suite = context.deserialize(json, clazz);
return Collections.singletonList(suite);
}
}
Class<E> is required in runtime to deserialise properly given JSON object. After that, let's create and customise Gson instance:
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.annotations.SerializedName;
import com.google.gson.reflect.TypeToken;
import java.io.File;
import java.io.FileReader;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class GsonApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
Type testCaseListType = new TypeToken<List<TestCase>>() {}.getType();
Type testSuiteListType = new TypeToken<List<TestSuite>>() {}.getType();
Gson gson = new GsonBuilder()
.registerTypeAdapter(testCaseListType, new OneOrManyJsonDeserializer<>(TestCase.class))
.registerTypeAdapter(testSuiteListType, new OneOrManyJsonDeserializer<>(TestSuite.class))
.setPrettyPrinting()
.create();
TestResponse response = gson.fromJson(new FileReader(jsonFile), TestResponse.class);
System.out.println(response);
}
}
As, you can see, we registered two instances for each one-to-many types. We need to use TypeToken to have correct mapping of our instances.
See also:
VERSION 2
After playing with above solution I came up with below deserialiser:
class OneOrManyJsonDeserializer implements JsonDeserializer<List<?>> {
@Override
public List<?> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
final Type elementType = $Gson$Types.getCollectionElementType(typeOfT, List.class);
if (json instanceof JsonArray) {
final JsonArray array = (JsonArray) json;
final int size = array.size();
if (size == 0) {
return Collections.emptyList();
}
final List<?> suites = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
suites.add(context.deserialize(array.get(i), elementType));
}
return suites;
}
Object suite = context.deserialize(json, elementType);
return Collections.singletonList(suite);
}
}
We do not need to customise it. Using $Gson$Types class we can get type of element and deserialise internal element. Simple usage:
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.annotations.SerializedName;
import com.google.gson.internal.$Gson$Types;
import java.io.File;
import java.io.FileReader;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class GsonApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
Gson gson = new GsonBuilder()
.registerTypeAdapter(List.class, new OneOrManyJsonDeserializer())
.setPrettyPrinting()
.create();
TestResponse response = gson.fromJson(new FileReader(jsonFile), TestResponse.class);
System.out.println(response);
}
}
Above code should also work for you.