2

I have a complex json as in here

I am trying to map this in my model class 'ChromeJsonModel' like :

Type collectionType = new TypeToken<List<ChromeJsonModel>>(){}.getType();
List<ChromeJsonModel> jsonModelList = (List<ChromeJsonModel>) new Gson().fromJson( jsonPrettyPrintString , collectionType);

Bu t I am receiving following error.

Expected BEGIN_ARRAY but was BEGIN_OBJECT

Any reason why and where am I going wrong?

Einstein_AB
  • 396
  • 5
  • 22
  • 1
    In `json` everything is one (main) object because you have `{ ... }` so use `ChromeJsonModel` instead of `List`. – Boken Mar 11 '19 at 09:39
  • Convert your list of object into JSON String. – Mebin Joe Mar 11 '19 at 09:42
  • @MebinJoe Do you mean like this: ChromeJsonModel jsonModelList = new Gson().fromJson( jsonPrettyPrintString , ChromeJsonModel.class); I tried this, but gives same error – Einstein_AB Mar 11 '19 at 09:45

3 Answers3

1

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.

Michał Ziober
  • 37,175
  • 18
  • 99
  • 146
  • 1
    This worked for me. Though using custom deserializer every time I have a situation like this is quite a pain. This is the only answers that attempts to resolve the problem. – Einstein_AB Mar 13 '19 at 09:07
  • @Einstein_AB, I'm glad to hear it works for you. I found out a little easier to accept solution. There is only one deserialiser we need to add this time. – Michał Ziober Mar 13 '19 at 18:56
0

I think you can use jackson.

ObjectMapper mapper = new ObjectMapper();
List<ChromeJsonModel> participantJsonList = mapper.readValue(jsonString, new TypeReference<List<ChromeJsonModel>>(){});
Tran Dung
  • 1
  • 1
0

In you json root element is json object:

{    <---- HERE YOU HAVE "OBJECT"

  "test-run": {
    "duration": 508.56199999999995,
    "result": "Passed",
    ...
   }

}

Change:

List<ChromeJsonModel> jsonModelList = (List<ChromeJsonModel>) ... ;

to:

ChromeJsonModel jsonModelList = (ChromeJsonModel) ... ;

You can try to generate POJO there: http://pojo.sodhanalibrary.com/

Boken
  • 4,825
  • 10
  • 32
  • 42