Search code examples
androidjsonretrofitretrofit2json-deserialization

Retrofit get infos from complex JSON response


I would like to retrieve and convert JSON response with retrofit into a list of Objects.

Here is an example of my JSON response with only two elements :

{
   "nhits":395,
   "parameters":{
      "dataset":[
         "sanisettesparis2011"
      ],
      "timezone":"UTC",
      "rows":2,
      "format":"json"
   },
   "records":[
      {
         "datasetid":"sanisettesparis2011",
         "recordid":"cb7aee1791ccce595e97d98fc0f72d05709abf52",
         "fields":{
            "objectid":10,
            "arrondissement":"02",
            "nom_voie":"BOULEVARD DE SEBASTOPOL",
            "geom_x_y":[
               48.864828018946774,
               2.351611260829617
            ],
            "geom":{
               "type":"Point",
               "coordinates":[
                  2.351611260829617,
                  48.864828018946774
               ]
            },
            "y":129375.048287,
            "x":601106.877435,
            "numero_voie":"85",
            "identifiant":"2/102",
            "horaires_ouverture":"6 h - 22 h"
         },
         "geometry":{
            "type":"Point",
            "coordinates":[
               2.351611260829617,
               48.864828018946774
            ]
         },
         "record_timestamp":"2018-09-30T22:00:19+00:00"
      },
      {
         "datasetid":"sanisettesparis2011",
         "recordid":"a47c22cf2fd31ba6b4a1ac6d1d2c699f92ee659a",
         "fields":{
            "objectid":12,
            "arrondissement":"02",
            "nom_voie":"RUE REAUMUR",
            "geom_x_y":[
               48.86679354449764,
               2.34957136374784
            ],
            "geom":{
               "type":"Point",
               "coordinates":[
                  2.34957136374784,
                  48.86679354449764
               ]
            },
            "y":129593.588071,
            "x":600957.183947,
            "numero_voie":"73",
            "identifiant":"2/105",
            "horaires_ouverture":"6 h - 22 h"
         },
         "geometry":{
            "type":"Point",
            "coordinates":[
               2.34957136374784,
               48.86679354449764
            ]
         },
         "record_timestamp":"2018-09-30T22:00:19+00:00"
      }
   ]
}

As you can see, this is a complex JSON response. The fields i'm interested in are the following :

  1. arrondissement
  2. nom_voie
  3. geom_x_y
  4. numero_voie
  5. horaires_ouverture

My retrofit implementation is the following :

RETROFITINSTANCE

public class RetrofitClientInstance {

    private static Retrofit retrofit;
    private static final String BASE_URL = "https://opendata.paris.fr/";

    public static Retrofit getRetrofitInstance() {
        if (retrofit == null) {
            Gson gson =
                    new GsonBuilder()
                            .registerTypeAdapter(Toilet.class, new MyDeserializer())
                            .create();

            retrofit = new retrofit2.Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create(gson))
                    .build();
        }
        return retrofit;
    }

}

MYDESERIALIZER

public class MyDeserializer implements JsonDeserializer<Toilet>
{
    @Override
    public Toiletdeserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
            throws JsonParseException
{
    // Get the "records" element from the parsed JSON
    JsonElement records = je.getAsJsonObject().get("records").getAsJsonArray();

    Log.d("test", records.toString());

    // Deserialize it. You use a new instance of Gson to avoid infinite recursion
    // to this deserializer
    return new Gson().fromJson(records, Toilet.class);
}
}

GETDATASERVICE

 public interface GetDataService {

    @GET("api/records/1.0/search/")
    Call<List<TestListModel>> getAllToilets(@Query("dataset") String dataset, @Query("rows") int numRows);
}

MAIN CALL

GetDataService service = RetrofitClientInstance.getRetrofitInstance().create(GetDataService.class);
    Call<List<Toilet>> call = service.getAllToilets("sanisettesparis2011", 1);
    call.enqueue(new Callback<List<Toilet>>() {



           @Override
            public void onResponse(Call<List<Toilet>> call, retrofit2.Response<List<Toilet>> response) {
                Log.d(TAG, response.body().toString());
            }

            @Override
            public void onFailure(Call<List<Toilet>> call, Throwable t) {
                t.printStackTrace();
                if (t instanceof IOException) {
                    Toast.makeText(SplashActivity.this, "Network error. Something went wrong...Please try later!", Toast.LENGTH_SHORT).show();
                }
                else {
                    Toast.makeText(SplashActivity.this, "Conversion issue", Toast.LENGTH_SHORT).show();
                }
            }
        });

TOILET MODEL

public class Toilet {
    @SerializedName("arrondissement")
    private int arrondissement;
    @SerializedName("nom_voie")
    private String street_name;
    @SerializedName("geom_x_y")
    private double[] coords;
    @SerializedName("numero_voie")
    private String street_number;
    @SerializedName("horaires_ouverture")
    private String opening_hours;

    public Toilet(int arrondissement, String street_name, double[] coords, String street_number, String opening_hours){
        this.arrondissement = arrondissement;
        this.street_name = street_name;
        this.coords = coords;
        this.street_number = street_number;
        this.opening_hours = opening_hours;
       }

    public int getArrondissement() {
        return arrondissement;
    }

    public void setArrondissement(int arrondissement) {
        this.arrondissement = arrondissement;
    }

    public String getStreet_name() {
        return street_name;
    }

    public void setStreet_name(String street_name) {
        this.street_name = street_name;
    }

    public double[] getCoords() {
        return coords;
    }

    public void setCoords(double[] coords) {
        this.coords = coords;
    }

    public String getStreet_number() {
        return street_number;
    }

    public void setStreet_number(String street_number) {
        this.street_number = street_number;
    }

    public String getOpening_hours() {
        return opening_hours;
    }

    public void setOpening_hours(String opening_hours) {
        this.opening_hours = opening_hours;
    }
}

When I start my app, I got this error : java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $

I suppose it means that my deserializer is not formatted corretly but I can't figure out how to implement retrofit correctly with this JSON response architecture.

The goal of this is to retrieve a list of Toilet Objects with the attributes I listed previously.

Thanks for your help !


Solution

  • Copy below Model class for your JSON

    import com.google.gson.annotations.SerializedName;
    
    import java.util.List;
    
    public class Toilet {
        /**
         * nhits : 395
         * parameters : {"dataset":["sanisettesparis2011"],"timezone":"UTC","rows":2,"format":"json"}
         * records : [{"datasetid":"sanisettesparis2011","recordid":"cb7aee1791ccce595e97d98fc0f72d05709abf52","fields":{"objectid":10,"arrondissement":"02","nom_voie":"BOULEVARD DE SEBASTOPOL","geom_x_y":[48.864828018946774,2.351611260829617],"geom":{"type":"Point","coordinates":[2.351611260829617,48.864828018946774]},"y":129375.048287,"x":601106.877435,"numero_voie":"85","identifiant":"2/102","horaires_ouverture":"6 h - 22 h"},"geometry":{"type":"Point","coordinates":[2.351611260829617,48.864828018946774]},"record_timestamp":"2018-09-30T22:00:19+00:00"},{"datasetid":"sanisettesparis2011","recordid":"a47c22cf2fd31ba6b4a1ac6d1d2c699f92ee659a","fields":{"objectid":12,"arrondissement":"02","nom_voie":"RUE REAUMUR","geom_x_y":[48.86679354449764,2.34957136374784],"geom":{"type":"Point","coordinates":[2.34957136374784,48.86679354449764]},"y":129593.588071,"x":600957.183947,"numero_voie":"73","identifiant":"2/105","horaires_ouverture":"6 h - 22 h"},"geometry":{"type":"Point","coordinates":[2.34957136374784,48.86679354449764]},"record_timestamp":"2018-09-30T22:00:19+00:00"}]
         */
    
        @SerializedName("nhits")
        private int nhits;
        @SerializedName("parameters")
        private ParametersBean parameters;
        @SerializedName("records")
        private List<RecordsBean> records;
    
        public int getNhits() {
            return nhits;
        }
    
        public void setNhits(int nhits) {
            this.nhits = nhits;
        }
    
        public ParametersBean getParameters() {
            return parameters;
        }
    
        public void setParameters(ParametersBean parameters) {
            this.parameters = parameters;
        }
    
        public List<RecordsBean> getRecords() {
            return records;
        }
    
        public void setRecords(List<RecordsBean> records) {
            this.records = records;
        }
    
        public static class ParametersBean {
            /**
             * dataset : ["sanisettesparis2011"]
             * timezone : UTC
             * rows : 2
             * format : json
             */
    
            @SerializedName("timezone")
            private String timezone;
            @SerializedName("rows")
            private int rows;
            @SerializedName("format")
            private String format;
            @SerializedName("dataset")
            private List<String> dataset;
    
            public String getTimezone() {
                return timezone;
            }
    
            public void setTimezone(String timezone) {
                this.timezone = timezone;
            }
    
            public int getRows() {
                return rows;
            }
    
            public void setRows(int rows) {
                this.rows = rows;
            }
    
            public String getFormat() {
                return format;
            }
    
            public void setFormat(String format) {
                this.format = format;
            }
    
            public List<String> getDataset() {
                return dataset;
            }
    
            public void setDataset(List<String> dataset) {
                this.dataset = dataset;
            }
        }
    
        public static class RecordsBean {
            /**
             * datasetid : sanisettesparis2011
             * recordid : cb7aee1791ccce595e97d98fc0f72d05709abf52
             * fields : {"objectid":10,"arrondissement":"02","nom_voie":"BOULEVARD DE SEBASTOPOL","geom_x_y":[48.864828018946774,2.351611260829617],"geom":{"type":"Point","coordinates":[2.351611260829617,48.864828018946774]},"y":129375.048287,"x":601106.877435,"numero_voie":"85","identifiant":"2/102","horaires_ouverture":"6 h - 22 h"}
             * geometry : {"type":"Point","coordinates":[2.351611260829617,48.864828018946774]}
             * record_timestamp : 2018-09-30T22:00:19+00:00
             */
    
            @SerializedName("datasetid")
            private String datasetid;
            @SerializedName("recordid")
            private String recordid;
            @SerializedName("fields")
            private FieldsBean fields;
            @SerializedName("geometry")
            private GeometryBean geometry;
            @SerializedName("record_timestamp")
            private String recordTimestamp;
    
            public String getDatasetid() {
                return datasetid;
            }
    
            public void setDatasetid(String datasetid) {
                this.datasetid = datasetid;
            }
    
            public String getRecordid() {
                return recordid;
            }
    
            public void setRecordid(String recordid) {
                this.recordid = recordid;
            }
    
            public FieldsBean getFields() {
                return fields;
            }
    
            public void setFields(FieldsBean fields) {
                this.fields = fields;
            }
    
            public GeometryBean getGeometry() {
                return geometry;
            }
    
            public void setGeometry(GeometryBean geometry) {
                this.geometry = geometry;
            }
    
            public String getRecordTimestamp() {
                return recordTimestamp;
            }
    
            public void setRecordTimestamp(String recordTimestamp) {
                this.recordTimestamp = recordTimestamp;
            }
    
            public static class FieldsBean {
                /**
                 * objectid : 10
                 * arrondissement : 02
                 * nom_voie : BOULEVARD DE SEBASTOPOL
                 * geom_x_y : [48.864828018946774,2.351611260829617]
                 * geom : {"type":"Point","coordinates":[2.351611260829617,48.864828018946774]}
                 * y : 129375.048287
                 * x : 601106.877435
                 * numero_voie : 85
                 * identifiant : 2/102
                 * horaires_ouverture : 6 h - 22 h
                 */
    
                @SerializedName("objectid")
                private int objectid;
                @SerializedName("arrondissement")
                private String arrondissement;
                @SerializedName("nom_voie")
                private String nomVoie;
                @SerializedName("geom")
                private GeomBean geom;
                @SerializedName("y")
                private double y;
                @SerializedName("x")
                private double x;
                @SerializedName("numero_voie")
                private String numeroVoie;
                @SerializedName("identifiant")
                private String identifiant;
                @SerializedName("horaires_ouverture")
                private String horairesOuverture;
                @SerializedName("geom_x_y")
                private List<Double> geomXY;
    
                public int getObjectid() {
                    return objectid;
                }
    
                public void setObjectid(int objectid) {
                    this.objectid = objectid;
                }
    
                public String getArrondissement() {
                    return arrondissement;
                }
    
                public void setArrondissement(String arrondissement) {
                    this.arrondissement = arrondissement;
                }
    
                public String getNomVoie() {
                    return nomVoie;
                }
    
                public void setNomVoie(String nomVoie) {
                    this.nomVoie = nomVoie;
                }
    
                public GeomBean getGeom() {
                    return geom;
                }
    
                public void setGeom(GeomBean geom) {
                    this.geom = geom;
                }
    
                public double getY() {
                    return y;
                }
    
                public void setY(double y) {
                    this.y = y;
                }
    
                public double getX() {
                    return x;
                }
    
                public void setX(double x) {
                    this.x = x;
                }
    
                public String getNumeroVoie() {
                    return numeroVoie;
                }
    
                public void setNumeroVoie(String numeroVoie) {
                    this.numeroVoie = numeroVoie;
                }
    
                public String getIdentifiant() {
                    return identifiant;
                }
    
                public void setIdentifiant(String identifiant) {
                    this.identifiant = identifiant;
                }
    
                public String getHorairesOuverture() {
                    return horairesOuverture;
                }
    
                public void setHorairesOuverture(String horairesOuverture) {
                    this.horairesOuverture = horairesOuverture;
                }
    
                public List<Double> getGeomXY() {
                    return geomXY;
                }
    
                public void setGeomXY(List<Double> geomXY) {
                    this.geomXY = geomXY;
                }
    
                public static class GeomBean {
                    /**
                     * type : Point
                     * coordinates : [2.351611260829617,48.864828018946774]
                     */
    
                    @SerializedName("type")
                    private String type;
                    @SerializedName("coordinates")
                    private List<Double> coordinates;
    
                    public String getType() {
                        return type;
                    }
    
                    public void setType(String type) {
                        this.type = type;
                    }
    
                    public List<Double> getCoordinates() {
                        return coordinates;
                    }
    
                    public void setCoordinates(List<Double> coordinates) {
                        this.coordinates = coordinates;
                    }
                }
            }
    
            public static class GeometryBean {
                /**
                 * type : Point
                 * coordinates : [2.351611260829617,48.864828018946774]
                 */
    
                @SerializedName("type")
                private String type;
                @SerializedName("coordinates")
                private List<Double> coordinates;
    
                public String getType() {
                    return type;
                }
    
                public void setType(String type) {
                    this.type = type;
                }
    
                public List<Double> getCoordinates() {
                    return coordinates;
                }
    
                public void setCoordinates(List<Double> coordinates) {
                    this.coordinates = coordinates;
                }
            }
        }
    }
    

    and while get response from retrofit get data like this

    here i use static position for get data

    GetDataService service = RetrofitClientInstance.getRetrofitInstance().create(GetDataService.class);
        Call<List<Toilet>> call = service.getAllToilets("sanisettesparis2011", 1);
        call.enqueue(new Callback<List<Toilet>>() {
    
    
    
               @Override
                public void onResponse(Call<List<Toilet>> call, retrofit2.Response<List<Toilet>> response) {
                    Log.d(TAG, response.body().toString());
    
    
     String arrondissement_0=response.body().getRecords().get(0).getFields().getArrondissement();
                        String arrondissement_1=response.body().getRecords().get(1).getFields().getArrondissement();
    
                        String nom_voie_0=response.body().getRecords().get(0).getFields().getNomVoie();
                        String nom_voie_1=response.body().getRecords().get(1).getFields().getNomVoie();
    
                        double geom_x_y_0=response.body().getRecords().get(0).getFields().getGeomXY().get(0);
                        double geom_x_y_1=response.body().getRecords().get(0).getFields().getGeomXY().get(1);
    
                        String numero_voie_0=response.body().getRecords().get(0).getFields().getNumeroVoie();
                        String numero_voie_1=response.body().getRecords().get(1).getFields().getNumeroVoie();
    
                        String horaires_ouverture_0=response.body().getRecords().get(0).getFields().getHorairesOuverture();
                        String horaires_ouverture_1=response.body().getRecords().get(1).getFields().getHorairesOuverture();
                }
    
                @Override
                public void onFailure(Call<List<Toilet>> call, Throwable t) {
                    t.printStackTrace();
                    if (t instanceof IOException) {
                        Toast.makeText(SplashActivity.this, "Network error. Something went wrong...Please try later!", Toast.LENGTH_SHORT).show();
                    }
                    else {
                        Toast.makeText(SplashActivity.this, "Conversion issue", Toast.LENGTH_SHORT).show();
                    }
                }
            });
    

    For creating a model class for any response you can use plugin is GsonFormat step : create empty class > press alt+insert > select GsonFormat > paste your JSON > Done check this image enter image description here