Niklaus
Niklaus

Reputation: 159

How to fix "Expected BEGIN_OBJECT but was BEGIN_ARRAY"?

I'm sending an HTTP request using Retrofit 2, I've done everything regularly, however, the situation is "not standard". I have a problem that one of the objects to receive in the response class is once sent as an object and in the other as a list. So I don't know whether to initialize it in the response class as an object or as an array.

Here is my full JSON response:

{
    "data": [
        {
            "name": "John",
            "surname": "Smith",
            "nicname": "Joe",
            "age": "32",
            "description": "Example",
            "state": "Nevada",
            "city": "Las Vegas",
            "job": "Security",
            "adress": "Some adress 1",
            "postcode": "412421",
            "details": {
                "intro": {
                    "title": "Mr.",
                      "married": "No",
                    "children": "2"
                },
                "rest": {
                    "pitctures":"24",
                    "chats": "7",
                    "lastChat": "12-01-2016",
                    "lastVisited": "07-04-2017",
                }
            }
        },
        {
            "name": "John",
            "surname": "Smith",
            "nicname": "Joe",
            "age": "32",
            "description": "Example",
            "state": "Nevada",
            "city": "Las Vegas",
            "job": "Security",
            "adress": "Some adress 1",
            "postcode": "412421",
            "details": {
                "intro": {
                    "title": "Mr.",
                      "married": "No",
                    "children": "No"
                },
                "rest": []
            }
        }
    ],
    "errors": false,
    "update_notifications": {
        "message": [],
        "friend_request": [],
        "code": "IzS0hivN1cyHBdygpeWv"
    }
}

Details.java class:

public class Details {

    @SerializedName("intro")
    @Expose
    private Intro intro;
    @SerializedName("rest")
    @Expose
    private Rest restObject;

    private ArrayList<Rest> restList;

    public Details(Intro intro, Rest restObject) {
        this.intro = intro;
        this.restObject = restObject;
    }

    public Details(Intro intro, ArrayList<Rest> restList) {
        this.intro = intro;
        this.restList = restList;
    }

    public Intro getIntro() {
        return intro;
    }

    public void setIntro(Intro intro) {
        this.intro = intro;
    }

    public Rest getRestObject() {
        return restObject;
    }

    public void setRestObject(Rest restObject) {
        this.restObject = restObject;
    }

    public ArrayList<Rest> getRestList() {
        return restList;
    }

    public void setRestList(ArrayList<Rest> restList) {
        this.restList = restList;
    }
}

And here is my CustomDeserializer.java (rest array neeed to be empty, maybe that's a problem) based on @Farid's answer:

public class CustomDeserializer implements JsonDeserializer<Details> {

    @Override
    public Details deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {

        JsonObject detailsObject = json.getAsJsonObject().get("details").getAsJsonObject();
        Details details;

        JsonObject introObject = detailsObject.get("intro").getAsJsonObject();
        String title = introObject.get("title").getAsString();
        String married = introObject.get("married").getAsString();
        String children = introObject.get("children").getAsString();

        try {
            JsonObject restObject = detailsObject.get("rest").getAsJsonObject();

            String pitctures = restObject.get("pitctures ").getAsString();
            String chats = restObject.get("chats ").getAsString();
            String lastChat = restObject.get("lastChat ").getAsString();
            String lastVisited = restObject.get("lastVisited ").getAsString();

            details = new Details(new Intro(title, married, children),
                      new Rest(pitctures, chats, lastChat, lastVisited));
        }catch (IllegalStateException e){
            JsonArray restArray = detailsObject.get("rest").getAsJsonArray();
            ArrayList<Rest> restList = new ArrayList<>();
            details = new Details(new Intro(title, married, children), restList);
        }
        return details;
    }
}

In MainActivity based on @Farid's answer:

Gson gsonConverter = new GsonBuilder().registerTypeAdapter(Details.class, new CustomDeserializer()).create();


        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(myUrl)
                .addConverterFactory(GsonConverterFactory.create(gsonConverter))
                .build();

        service1 = retrofit.create(MyAPI.class);

        final ProgressDialog progressDialog = new ProgressDialog(this);
        progressDialog.setCancelable(false);
        progressDialog.setMessage("Please wait!");
        progressDialog.show();

        final MyRequest myRequest = new MyRequest();

        myRequest.setPin(pin);

        final Call<MyResponse> myResponseCall = service1.get (code, myRequest);

        myResponseCall.enqueue(new Callback<MyResponse>() {
                @Override
                public void onResponse(Call<MyResponse> call, Response<MyResponse> response) {
                }
            });

Upvotes: 0

Views: 386

Answers (1)

Farid
Farid

Reputation: 2562

It's possible. You'll have to create a custom Deserializer. As you can see there is JsonDeserializer<Details> whose type parameter is Details that means any time GSON tries to deserialize Details object, it will call CustomDeserializer to deserialize it. Anything inside Details object should be deserialized manually as seen in the CustomDeserializer class whereas all other types outside Details class (e.g. Class, String, int) will be handled by GSON seamlessly

CustomDeserializer.java

    public class CustomDeserializer implements JsonDeserializer<Details> {

    @Override
    public Details deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {

        JsonObject detailsObject = json.getAsJsonObject();
        Details details;
        Intro intro = new Intro(detailsObject.get("intro").getAsJsonObject().get("example").getAsString());

        try {
            JsonObject restObject = detailsObject.get("rest").getAsJsonObject();
            String example = restObject.get("example").getAsString();

            details = new Details(intro, new Rest(example));
        }catch (IllegalStateException e){ // In case rest is ArrayList
            JsonArray restArray = detailsObject.get("rest").getAsJsonArray();
            ArrayList<Rest> resList = new ArrayList<>();
            for (JsonElement element: restArray){
                JsonObject restObject = element.getAsJsonObject();
                String example = restObject.get("example").getAsString();
                resList.add(new Rest(example));
            }

            details = new Details(intro, resList);
        }

        return details;
    }
}

Then you have to register CustomDeserializer as TypeAdapter as below:

 Gson gsonConverter = GsonBuilder().registerTypeAdapter(Details::class.java,
 CustomDeserializer()).create();

 Retrofit retrofit = new Retrofit.Builder()
 .addConverterFactory(GsonConverterFactory.create(gsonConverter))
 ///Rest of Retrofit builder code

If you want to know more about custom deserialization, search for "gson deserialization" and find a tutorial that appeals to you most.


Below code snippets are OP specific classes

Response.java

public class Response {

    private List<DataItem> data = null;
    private Boolean errors;
    private String example;

    public List<DataItem> getData() {
        return data;
    }

    public void setData(List<DataItem> data) {
        this.data = data;
    }

    public Boolean getErrors() {
        return errors;
    }

    public void setErrors(Boolean errors) {
        this.errors = errors;
    }

    public String getExample() {
        return example;
    }

    public void setExample(String example) {
        this.example = example;
    }

}

Data.java

 public class DataItem { 
    private String example;
    private Details details;

    public String getExample() {
        return example;
    }

    public void setExample(String example) {
        this.example = example;
    }

    public Details getDetails() {
        return details;
    }

    public void setDetails(Details details) {
        this.details = details;
    }
}

Details.java

public class Details {

    private Intro intro;
    private Rest restObject;
    private ArrayList<Rest> restList;

    public Details(Intro intro, ArrayList<Rest> restList) {
        this.intro = intro;
        this.restList = restList;
    }

    public Details(Intro intro, Rest restObject) {
        this.intro = intro;
        this.restObject = restObject;
    }

    public Rest getRestObject() {
        return restObject;
    }

    public ArrayList<Rest> getRestList() {
        return restList;
    }

    public Intro getIntro() {
        return intro;
    }

    public void setIntro(Intro intro) {
        this.intro = intro;
    }
}

Intro.java

public class Intro {

    public Intro(String example) {
        this.example = example;
    }

    private String example;

    public String getExample() {
        return example;
    }

    public void setExample(String example) {
        this.example = example;
    }

}

Rest.java

public class Rest {
    private String example;

    public Rest(String example) {
        this.example = example;
    }

    public String getExample() {
        return example;
    }
}

Upvotes: 1

Related Questions