Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 With WebService JAX-RS and Android Retrofit

2

I'm studying WebService and consumption on Android with Retrofit2.

I've done tests with public APIs like ViaCEP and FIPE and I can use the retrofit quietly, very easy but when I set up my own webservice using JAX-RS with Jersey I have problems. I'll post the details so you can help me if you can.

The error message is:

  

Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2

Result when calling resource in WebService - JSON file:

URL: http: // MEUSERVER: 8080 / movie director / movies

{
  "filmes": {
    "filme": [
      {
        "id": 1,
        "titulo": "E o vento levou",
        "ano": "1961-01-12T00:00:00-03:00",
        "idioma": "Português",
        "atorPrincipal": "ValdikSoriano",
        "locado": false,
        "valorDiaria": 2.65
      },
      {
        "id": 2,
        "titulo": "Titanic",
        "ano": "1998-01-12T00:00:00-02:00",
        "idioma": "Português",
        "atorPrincipal": "Dicaprio",
        "locado": false,
        "valorDiaria": 2.65
      }
    ]
  }
}

My web.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
  <display-name>LocadoraFilmes</display-name>

    <servlet>
        <servlet-name>Jersey REST Service</servlet-name>
        <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
        <init-param>
            <param-name>javax.ws.rs.Application</param-name>
            <param-value>service.ApplicationJAXRS</param-value>
        </init-param>
    </servlet>

    <servlet-mapping>
        <servlet-name>Jersey REST Service</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>  

</web-app>

My file ApplicationJAXRS.java :

@ApplicationPath("resources")
public class ApplicationJAXRS extends Application {    

    @Override
    public Set<Object> getSingletons() {
        Set<Object> singletons = new HashSet<>();

        singletons.add(new JettisonFeature());
        return singletons;
    }       

    @Override
    public Set<Class<?>> getClasses() {
        Set<Class<?>> classes = new HashSet<>();
        classes.add(FilmeService.class);
        return classes;
    }               

}

The project I did is using JPA, so there are JPA annotation besides JAX-RS:

File Filme.java :

@Entity(name = "filme")
@Table(name = "filme")
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Filme implements Serializable{

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @XmlElement(name = "id")
    private Integer id;

    @Column(nullable = false)
    private String titulo;

    @Column(nullable = false)
    @Temporal(TemporalType.DATE)
    private Date ano;

    @Column(nullable = false)
    private String idioma;

    @Column(nullable = false)
    private String atorPrincipal;

    @Column(nullable = false)
    private Boolean locado = false;

    @Column(nullable = true)
    private Float valorDiaria;


    public Filme() {

    }


    public Filme(Integer id, String titulo, Date ano, String atorPrincipal, Boolean locado, Float valorDiaria) {        
        this.id = id;
        this.titulo = titulo;
        this.ano = ano;
        this.atorPrincipal = atorPrincipal;
        this.locado = locado;
        this.valorDiaria = valorDiaria;
    }

//GET and SET

}

File FilmeService.java :

@Path("/filmes")
@Produces({MediaType.APPLICATION_JSON + ";charset=UTF-8"})
@Consumes({MediaType.APPLICATION_JSON + ";charset=UTF-8"})
public class FilmeService {

    private FilmeDAO daoFilme = new FilmeDAO();

    @GET
    public List<Filme> listarFilmes(){

        return daoFilme.findAll();
    }

    @GET
    @Path(value = "{id}")
    public Filme getFilmeByID(@PathParam(value="id")int id){
        return daoFilme.findById(id);
    }
}

On the Android device, as I mentioned, I use the retrofit. Here is the FilmeAPI.java file there:

public interface FilmeAPI {

    @GET("filmes")
    Call<List<Filme>> getFilmes();


    //Gson gson = new GsonBuilder().registerTypeAdapter(Filme.class, new FilmeDeserializer()).create();

    Gson gson = new GsonBuilder().setLenient().create();

    public static final Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("http://192.168.25.2:8080/locadorafilmes-0.0.1-SNAPSHOT/")
            .addConverterFactory(GsonConverterFactory.create(gson))
            .build();

}

The Filme.java class in android:

public class Filme implements Serializable{

    private static final long serialVersionUID = 1L;

    private Integer id;
    private String titulo;
    private Date ano;
    private String idioma;
    private String atorPrincipal;
    private Boolean locado = false;
    private Float valorDiaria;

//GET and SET

}

And the file MainActivity.java :

public class MainActivity extends AppCompatActivity {
    private ListView lvFilmes;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        lvFilmes = (ListView) findViewById(R.id.listview_filmes);
        connectWebServiceFilmes();

    }


    private void connectWebServiceFilmes(){

        FilmeAPI filmeAPI = FilmeAPI.retrofit.create(FilmeAPI.class);

        Call<List<Filme>> callFilme = filmeAPI.getFilmes();

        callFilme.enqueue(new Callback<List<Filme>>() {
            @Override
            public void onResponse(Call<List<Filme>> call, Response<List<Filme>> response) {
                Log.i("Teste", "Dentro do onResponse");
                List<Filme> filmes = new ArrayList<Filme>();
                if (response.body()!=null){
                    Log.i("Teste", "Response não esta vazio! " + response.isSuccessful());
                    filmes.addAll(response.body());
                    if (filmes!=null){
                        onUpdateListViewFilmes(getBaseContext(), filmes);
                    }
                    else{
                        Log.e("Teste", "Array de Filmes vazio!");
                    }
                }
            }

            @Override
            public void onFailure(Call<List<Filme>> call, Throwable t) {
                Log.e("Teste", "Erro ao baixar dados. Mensagem: " + t.getMessage() +
                        " \n Local Mensagem: " + t.getLocalizedMessage() +
                        " \n TrackTrace: " + t.getStackTrace());
            }
        });


    }

    private void onUpdateListViewFilmes(Context context, List<Filme> filmes){

        FilmeAdapter adapter = new FilmeAdapter(context, filmes);
        lvFilmes.setAdapter(adapter);


    }

}
    
asked by anonymous 16.01.2017 / 18:59

2 answers

1

Well, I was able to solve the problem, it was simple:

First, just to state and reaffirm, the problem was the way the JAX-RS webservice was generating the JSON kind of paused!

{
  "filmes": {
    "filme": [
      {
        "id": 1,
        "titulo": "E o vento levou",
        "ano": "1961-01-12T00:00:00-03:00",
        "idioma": "Português",
        "atorPrincipal": "ValdikSoriano",
        "locado": false,
        "valorDiaria": 2.65
      },
      {
        "id": 2,
        "titulo": "Titanic",
        "ano": "1998-01-12T00:00:00-02:00",
        "idioma": "Português",
        "atorPrincipal": "Dicaprio",
        "locado": false,
        "valorDiaria": 2.65
      }
    ]
  }
}

It should look like this:

[
  {
    "id": 1,
    "titulo": "E o vento levou",
    "ano": "1961-01-12",
    "idioma": "Português",
    "ator": "ValdikSoriano",
    "locado": false,
    "valor": 2.65
  },
  {
    "id": 2,
    "titulo": "Titanic",
    "ano": "1998-01-12",
    "idioma": "Português",
    "ator": "Dicaprio",
    "locado": false,
    "valor": 2.65
  }
]

That is, instead of sending an Object Array it was sending a Movies object that contained another object with the movies. At least that's what I meant. I found a solution right here in stackoverflow:

link

In summary:

In my MovieService that was like this:

@Path("/filmes")
@Produces({MediaType.APPLICATION_JSON + ";charset=UTF-8"})
@Consumes({MediaType.APPLICATION_JSON + ";charset=UTF-8"})
public class FilmeService {

    private FilmeDAO daoFilme = new FilmeDAO();

    @GET
    public List<Filme> listarFilmes(){

        return daoFilme.findAll();
    }

    @GET
    @Path(value = "{id}")
    public Filme getFilmeByID(@PathParam(value="id")int id){
        return daoFilme.findById(id);
    }
}

It looks like this:

@Path("/filmes")
@Produces({MediaType.APPLICATION_JSON + ";charset=UTF-8"})
@Consumes({MediaType.APPLICATION_JSON + ";charset=UTF-8"})
public class FilmeService {

    private FilmeDAO daoFilme = new FilmeDAO();

    @GET
    public String listarFilmes(){

        return daoFilme.findAll().toString();
    }

    @GET
    @Path(value = "{id}")
    public Filme getFilmeByID(@PathParam(value="id")int id){
        return daoFilme.findById(id);
    }
}

I just changed the ListStream method to return a String and returned the List as a string using the toString method of the list itself.

And the Movie.java made two changes basically. I added the annotation @XmlElement to each of the attributes by giving them a nickname with the annotation name parameter. I also changed the toString using the JSONObject passing the parametos as an array using its put method and in the end calling the toString to return with the toString method of the Movie

    @Entity(name = "filme")
        @Table(name = "filme")
        @XmlRootElement
        @XmlAccessorType(XmlAccessType.FIELD)
        public class Filme implements Serializable{

        private static final long serialVersionUID = 1L;

        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @XmlElement(name = "id")
        private Integer id;

        @Column(nullable = false)
        @XmlElement(name = "titulo")
        private String titulo;

        @Column(nullable = false)
        @XmlElement(name = "ano")
        @Temporal(TemporalType.DATE)
        private Date ano;

        ....

        //GET e SET

        @Override
        public String toString() {
            try {
                JSONObject o = new JSONObject().put("id", id).put("titulo", titulo).put("ano", ano).put("idioma", idioma)
                        .put("ator", atorPrincipal).put("locado", locado).put("valor", valorDiaria);
                return o.toString();
            } catch (JSONException e) {
                System.out.println("Erro no toString do Filme JSON: "+ e.getMessage());
            }
            return null;
        }
}

PS: Thank you guys that you answered to help me both here and in the other link of the other question !!!!!

    
16.01.2017 / 23:24
1

Add the following classes, on both the server and android:

public class ConjuntoFilmes implements Serializable {

    private static final long serialVersionUID = 1L;

    private ConjuntoFilmesInterno filmes;

    public ConjuntoFilmes(ConjuntoFilmesInterno filmes) {
        this.filmes = filmes;
    }

    // Getter e setter, se precisar.
}
public class ConjuntoFilmesInterno implements Serializable {

    private static final long serialVersionUID = 1L;

    private List<Filme> filme;

    public ConjuntoFilmesInterno(List<Filme> filme) {
        this.filme = filme;
    }

    // Getter e setter, se precisar.
}

It should be noted that although these classes have the same code both on the server and on the android, they are actually different classes, since the class Filme that a reference is that of the server and that of the other is the Android However, despite this difference, the code stays the same.

No FilmeService , change this method:

@GET
public List<Filme> listarFilmes(){

    return daoFilme.findAll();
}

And leave it like this:

@GET
public ConjuntoFilmes listarFilmes() {
    List<Filme> filmes = daoFilme.findAll();
    return new ConjuntoFilmes(new ConjuntoFilmesInterno(filmes));
}

No FilmeAPI , change this:

@GET("filmes")
Call<List<Filme>> getFilmes();

For this:

@GET("filmes")
Call<ConjuntoFilmes> getFilmes();

The reason for this is in your JSON. See the comments I've added:

// Se começa com "{", tem que ser um objeto. Esse é o ConjuntoFilmes.
{

  // O ConjuntoFilmes tem um campo "filmes" que é outro objeto, o ConjuntoFilmesInterno.
  "filmes": {

    // O ConjuntoFilmesInterno tem um campo "filme", que é uma List<Filme>.
    "filme": [...] // Isso é um array de 
  }
}

However, this structure is not legal, since having both ConjuntoFilmes and ConjuntoFilmesInterno is unnecessary. So, if you can change the structure of JSON , I recommend doing so:

public class ConjuntoFilmes implements Serializable {

    private static final long serialVersionUID = 1L;

    private List<Filme> filmes;

    public ConjuntoFilmes(List<Filme> filmes) {
        this.filmes = filmes;
    }

    // Getter e setter, se precisar.
}
@GET
public ConjuntoFilmes listarFilmes() {
    ConjuntoFilmes filmes = daoFilme.findAll();
    return new ConjuntoFilmes(filmes);
}
    
16.01.2017 / 19:21