web-dev-qa-db-fra.com

Impossible de désérialiser une instance de Java.util.ArrayList hors de VALUE_STRING

J'ai un service REST créé avec Jersey et déployé dans AppEngine. Le service REST implémente le verbe PUT qui utilise un type de support application/json. Les données la liaison est effectuée par Jackson.

Le verbe utilise une relation entreprise-départements représentée dans JSON en tant que

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

Du côté client, j'utilise gson pour convertir la représentation JSON en un objet Java. Ensuite, je passe l'objet à mon REST et tout fonctionne correctement .

Problème:

Lorsque ma représentation JSON n'a qu'un seul élément dans la collection

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

le service ne peut pas désérialiser l'objet.

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

Comme indiqué par d'autres utilisateurs, la solution consiste à ajouter l'indicateur ACCEPT_SINGLE_VALUE_AS_ARRAY (par exemple, Jersey: impossible de désérialiser l'instance de ArrayList hors chaîne ). Néanmoins, je ne contrôle pas un ObjectMapper car, côté service, il est créé de manière transparente par Jackson.

Question:

Existe-t-il un moyen de configurer ObjectMapper côté service pour activer ACCEPT_SINGLE_VALUE_AS_ARRAY? des annotations? web.xml?

Détails du code

Objet 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;
    }
}

Le service REST:

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

Côté client:

...
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());
}
...
47
Manolo

Voici la solution à mon ancienne question:

J'ai implémenté mon propre ContextResolver afin d'activer la fonctionnalité DeserializationConfig.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY.

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;
   }
}

Et dans le web.xml, j'ai enregistré mon paquet dans la définition du 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>

... tout le reste est fait de manière transparente par jersey/jackson.

49
Manolo

La définition de cet attribut sur l'instance ObjectMapper fonctionne,

objectMapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
21
Kannan Ramamoorthy

essayez-vous

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

l'accolade carrée est le point clé.

19
Richard Chen

Pour les personnes qui trouvent cette question en recherchant le message d'erreur, vous pouvez également voir cette erreur si vous faites une erreur dans votre @JsonProperty annotations telles que vous annotez une propriété List- typée avec le nom d’un champ à valeur unique:

@JsonProperty("someSingleValuedField") // Oops, should have been "someMultiValuedField"
public List<String> getMyField() { // deserialization fails - single value into List
  return myField;
}
6
Andrew Spencer

from Jackson 2.7.x +, il existe un moyen d'annoter la variable membre elle-même:

 @JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
 private List<String> newsletters;

Plus d'infos ici: Jackson @JsonFormat

3
theINtoy