No se puede deserializar instancia de java.útil.ArrayList fuera de CADENA DE VALOR


Tengo un servicio REST construido con Jersey y desplegado en el AppEngine. El servicio REST implementa el verbo PUT que consume un tipo de medio application / json. El enlace de datos es realizado por Jackson.

El verbo consume una relación empresa-departamentos representada en JSON como

{"name":"myEnterprise", "departments":["HR","IT","SC"]}

En el lado del cliente, uso gson para convertir la representación JSON en un objeto java. Entonces, paso el objeto a mi servicio de DESCANSO y funciona fino.

Problema:

Cuando mi representación JSON solo tiene un elemento en la colección

{"name":"myEnterprise", "departments":["HR"]}

El servicio no puede deserializar el objeto.

ATTENTION: /enterprise/enterprise: org.codehaus.jackson.map.JsonMappingException: 
Can not deserialize instance of java.util.ArrayList out of VALUE_STRING token at 
[Source: org.mortbay.jetty.HttpParser$Input@5a9c5842; line: 1, column: 2

Como informaron otros usuarios, la solución es agregar la bandera ACCEPT_SINGLE_VALUE_AS_ARRAY (por ejemplo, Jersey: No se puede deserializar la instancia de ArrayList fuera de la cadena). Sin embargo, no estoy controlando un ObjectMapper porque en el lado del servicio está hecho de forma transparente por Jackson.

Pregunta:

¿Hay alguna forma de configurar el ObjectMapper en el lado del servicio para habilitar ACCEPT_SINGLE_VALUE_AS_ARRAY? anotaciones? web.xml?

Detalles del código

Objeto Java:

@XmlRootElement
public class Enterprise {
    private String name;
    private List<String> departments;

    public Enterprise() {}

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public List<String> getDepartments() {
        return departments;
    }
    public void setDepartments(List<String> departments) {
        this.departments = departments;
    }
}

El RESTO del lado del servicio:

    @PUT
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    @Path("/enterprise") 
    public Response putEnterprise(Enterprise enterprise,
            @Context HttpServletRequest req){
         ...
    }

Lado del cliente:

...
String jsonString = "{\"name\":\"myEnterprise\", \"departments\":[\"HR\"]}";
Enterprise enterprise = gson.fromJson(jsonString, Enterprise.class);
System.out.println(gson.toJson(enterprise));
response = webResource              
           .type(MediaType.APPLICATION_JSON)
           .put(ClientResponse.class,enterprise);
if (response.getStatus() >= 400) {
        throw new RuntimeException("Failed : HTTP error code : " + response.getStatus());
}
...
Author: Community, 2013-01-29

4 answers

Esta es la solución para mi vieja pregunta:

Implementé mi propio ContextResolver para habilitar DeserializationConfig.Función.ACCEPT_SINGLE_VALUE_AS_ARRAY característica.

package org.lig.hadas.services.mapper;

import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;

import org.codehaus.jackson.map.DeserializationConfig;
import org.codehaus.jackson.map.ObjectMapper;

@Produces(MediaType.APPLICATION_JSON)
@Provider
public class ObjectMapperProvider implements ContextResolver<ObjectMapper>
{
   ObjectMapper mapper;

   public ObjectMapperProvider(){
       mapper = new ObjectMapper();
       mapper.configure(DeserializationConfig.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
   }
   @Override
   public ObjectMapper getContext(Class<?> type) {
       return mapper;
   }
}

Y en la web.xml Registré mi paquete en la definición de servlet...

<servlet>
    <servlet-name>...</servlet-name>
    <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
    <init-param>
        <param-name>com.sun.jersey.config.property.packages</param-name>
        <param-value>...;org.lig.hadas.services.mapper</param-value>        
    </init-param>
    ...
</servlet>

... todo el resto está hecho de forma transparente por jersey / Jackson.

 44
Author: Manolo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-04-17 23:19:23

¿Intentas

[{"name":"myEnterprise", "departments":["HR"]}]

La llave cuadrada es el punto clave.

 15
Author: Richard Chen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-04-09 10:10:24

Establecer este atributo como instancia ObjectMapper funciona,

objectMapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
 13
Author: Kannan Ramamoorthy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-27 08:12:35

Para las personas que encuentran esta pregunta buscando el mensaje de error, también puede ver este error si comete un error en sus @JsonProperty anotaciones de tal manera que anota una propiedad List con el nombre de un campo de un solo valor:

@JsonProperty("someSingleValuedField") // Oops, should have been "someMultiValuedField"
public List<String> getMyField() { // deserialization fails - single value into List
  return myField;
}
 4
Author: Andrew Spencer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-10 13:54:57