Jackson deserialization - with contained ArrayList<T>

jackson, json

Solution

Your problem is that the 'object' property value is a String and not an array! The string contains a JSON array but Jackson expects a native array (without the wrapping quotes).

I had the same problem and I created a custom deserializer, which will deserialize a string value to a generic collection of the desired type:

public class JsonCollectionDeserializer extends StdDeserializer<Object> implements ContextualDeserializer {

  private final BeanProperty    property;

  /**
   * Default constructor needed by Jackson to be able to call 'createContextual'.
   * Beware, that the object created here will cause a NPE when used for deserializing!
   */
  public JsonCollectionDeserializer() {
    super(Collection.class);
    this.property = null;
  }

  /**
   * Constructor for the actual object to be used for deserializing.
   *
   * @param property this is the property/field which is to be serialized
   */
  private JsonCollectionDeserializer(BeanProperty property) {
    super(property.getType());
    this.property = property;
  }

  @Override
  public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException {
    return new JsonCollectionDeserializer(property);
  }


  @Override
  public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
    switch (jp.getCurrentToken()) {
      case VALUE_STRING:
        // value is a string but we want it to be something else: unescape the string and convert it
        return JacksonUtil.MAPPER.readValue(StringUtil.unescapeXml(jp.getText()), property.getType());
      default:
        // continue as normal: find the correct deserializer for the type and call it
        return ctxt.findContextualValueDeserializer(property.getType(), property).deserialize(jp, ctxt);
    }
  }
}

Note that this deserializer will also work if the value actually is an array and not a string, because it delegates the actual deserialization accordingly.

In your example you would now have to annotate your collection field like so:

public class ListResponse { 

    public String error;    
    @JsonDeserialize(using = JsonCollectionDeserializer.class)
    public ArrayList<ListItem> object;  

    public ListResponse() {}    
}

And that should be it.

Note: JacksonUtil and StringUtil are custom classes, but you can easily replace them. For example by using `new ObjectMapper()` and `org.apache.commons.lang3.StringEscapeUtils`.

Problem

Good day, I am currently integration attempting to consume a REST service that produces JSON (written in .NET) using Jackson (with Jersey). The JSON consists of a possible error message and an array of objects. Below is a sample of the JSON returned as produced by Jersey's logging filter: ``` { "error":null, "object":"[{\"Id\":16,\"Class\":\"ReportType\",\"ClassID\":\"4\",\"ListItemParent_ID\":4,\"Item\":\"Pothole\",\"Description\":\"Pothole\",\"Sequence\":1,\"LastEditDate\":null,\"LastEditor\":null,\"ItemStatus\":\"Active\",\"ItemColor\":\"#00AF64\"}]" } ``` I have two classes to represent the type (the outer ListResponse): ``` public class ListResponse { public String error; public ArrayList<ListItem> object; public ListResponse() { } } ``` and (the inner ListItem): ``` public class ListItem { @JsonProperty("Id") public int id; @JsonProperty("Class") public String classType; @JsonProperty("ClassID") public String classId; @JsonProperty("ListItemParent_ID") public int parentId; @JsonProperty("Item") public String item; @JsonProperty("Description") public String description; @JsonAnySetter public void handleUnknown(String key, Object value) {} public ListItem() { } } ``` The class that invokes and returns the JSON looks like this: ``` public class CitizenPlusService { private Client client = null; private WebResource service = null; public CitizenPlusService() { initializeService("http://localhost:59105/PlusService/"); } private void initializeService(String baseURI) { // Use the default client configuration. ClientConfig clientConfig = new DefaultClientConfig(); clientConfig.getClasses().add(JacksonJsonProvider.class); client = Client.create(clientConfig); // Add a logging filter to track communication between server and client. client.addFilter(new LoggingFilter()); // Add the base URI service = client.resource(UriBuilder.fromUri(baseURI).build()); } public ListResponse getListItems(String id) throws Exception { ListResponse response = service.path("GetListItems").path(id).accept(MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_XML_TYPE).get(ListResponse.class); return response; } } ``` The important call here is the getListItems method. Running the code in a test harness, produces the following: ``` org.codehaus.jackson.map.JsonMappingException: Can not deserialize instance of java.util.ArrayList out of VALUE_STRING token at [Source: java.io.StringReader@49497eb8; line: 1, column: 14] (through reference chain: citizenplus.types.ListResponse["object"]) ``` Please assist. Regards, Carl-Peter Meyer

Original source

Related problems