Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 Com Webservice JAX-RS e Android Retrofit

Asked

Viewed 5,541 times

4

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 the resource in Webservice - JSON file:

URL: http://MEUSERVER:8080/movie/movie rentals

{
  "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’s JPA Annotation in addition to JAX-RS:

Filing cabinet 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

}

Filing cabinet 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 Android device, as already mentioned, I use the retrofit. Here’s the file FilmeAPI.java there is 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 class Filme.java on 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);


    }

}
  • The problem seems to be in the structure of your JSON. The client expects an array but receives an object filmes, being that it is inside that object that the array is filme. Which server are you using? A user on Soen said they faced a problem when using Jersey 2.x with Glassfish 3.x:link

  • I’m wearing Wildfly 10

2 answers

3


Well person, I managed to solve the problem, it was simple:

First, just to state and reaffirm, the problem was the way the JAX-RS web service was generating the JSON half passionate!

{
  "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 be 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 Array of Objects he was sending a Movies object that contained another object with the movies. At least that’s what you implied. I found a solution right here in stackoverflow:

https://stackoverflow.com/questions/10849526/return-jsonarray-instead-of-jsonobject-jersey-jax-rs

In short:

In my Filmeservice it 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);
    }
}

Stayed 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 only changed the signature of the listFilmes method to return a String and returned the List as a string using the toString method from the list itself.

And Filme.java basically made two changes. I added the Annotation @Xmlelement to each of the attributes giving them a nickname with the parameter name of the Annotation. I also changed toString using Jsonobject by passing the parameters as an array using its put method and then calling toString to return with the toString method from 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.: Thanks staff ai who answered to help me both here and on the other link of the other question!!!!!

1

Add the following classes, both on the server and on 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 android, they are actually different classes, since the class Filme that one reference is the server and the other is the android. However, despite this difference, the code is equal.

In the FilmeService, change this method:

@GET
public List<Filme> listarFilmes(){

    return daoFilme.findAll();
}

And leave it so:

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

In the FilmeAPI, change that:

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

For that reason:

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

The reason for this is in your JSON. See the comments I 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 the ConjuntoFilmes and the ConjuntoFilmesInterno is unnecessary. Therefore, if you can change the JSON structure, 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);
}
  • Yeah, what I wanted to do was just change Json but I can’t figure out how to change it, everything is done by JAX-RS.

  • @Hugo If you simply return one ConjuntoFilmes and see what comes out as JSON on the other side, what happens?

Browser other questions tagged

You are not signed in. Login or sign up in order to post.