Десериализация Джексона - с содержащимся ArrayList <T>

Добрый день,

В настоящее время я пытаюсь использовать службу REST, которая создает JSON (написанный на .NET), используя Джексона (с Джерси). JSON состоит из сообщения о возможной ошибке и массива объектов. Ниже приведен пример JSON-файла, возвращенного фильтром журналирования Джерси:

{
    "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\"}]"
}

У меня есть два класса для представления типа (внешний ListResponse):

public class ListResponse { 

    public String error;    
    public ArrayList<ListItem> object;  

    public ListResponse() { 
    }
}

и (внутренний 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() {
    }
}

Класс, который вызывает и возвращает JSON, выглядит следующим образом:

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

Важным вызовом здесь является метод getListItems. Запустив код в тестовом жгуте, выдает следующее:

org.codehaus.jackson.map.JsonMappingException: Can not deserialize instance of java.util.ArrayList out of VALUE_STRING token
at [Source: [email protected]; line: 1, column: 14] (through reference chain: citizenplus.types.ListResponse["object"])

Пожалуйста помогите.

С Уважением, Карл-Питер Мейер

 varaprakash02 окт. 2012 г., 23:07
Удалось ли вам решить эту проблему?
 Philipp Maschke23 мая 2013 г., 13:43
Я думаю, что проблема здесь во входном JSON: «объект»; свойство состоит изString which contains a JSON array но Джексон ожидает собственный массив (без апострофии). У меня сейчас такая же проблема ... Если я найду решение, я позволю вам знать;)
 Manolo07 февр. 2013 г., 21:10
мой ответ может помочь вам, если вы найдете способ изменить поддержку JSON в .NETstackoverflow.com/a/14760003/2022175

Ответы на вопрос(3)

Решение Вопроса

что «объект» Значение свойства - это строка, а не массив! Строка содержит массив JSON, но Джексон ожидает собственный массив (без кавычек).

У меня была та же проблема, и я создал собственный десериализатор, который десериализует строковое значение в общую коллекцию нужного типа:

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

Обратите внимание, что этот десериализатор также будет работать, если значение фактически является массивом, а не строкой, потому что он соответственно делегирует фактическую десериализацию.

В вашем примере вы теперь должны аннотировать поле вашей коллекции следующим образом:

public class ListResponse { 

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

    public ListResponse() {}    
}

И это должно быть.

Примечание: JacksonUtil и StringUtil являются пользовательскими классами, но вы можете легко заменить их. Например, используяnew ObjectMapper() а такжеorg.apache.commons.lang3.StringEscapeUtils.

 15 июн. 2015 г., 18:33
Мне нравится это решение, но в некоторых случаях я вижу переполнение стека. Конкретно у меня естьList<List<Integer>> поле. В этом случае,return ctxt.findContextualValueDeserializer(property.getType(), property).deserialize(jp, ctxt); бесконечно повторяется при опросе внутреннего списка. У меня не было возможности глубоко погрузиться в это, но я верю в это, потому что переданное свойство основано на внешнем списке, а не на внутреннем.

@JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="type")
public interface Geometry {

}

public class Point implements Geometry{
 private String type="Point";
  ....
}
public class Polygon implements Geometry{
   private String type="Polygon";
  ....
}
public class LineString implements Geometry{
  private String type="LineString";
  ....
}


GeoJson geojson= null;
ObjectMapper mapper = new ObjectMapper();
mapper.disable(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.registerSubtypes(Polygon.class,LineString.class,Point.class);
try {
    geojson=mapper.readValue(source, GeoJson.class);

} catch (IOException e) {
    e.printStackTrace();
}

@JsonDeserialize атрибут как информация о типе теряется в обобщениях во время выполнения. Также вы должны избегать использования конкретных классов для коллекций, если можете.

public class ListResponse { 

    public String error;

    @JsonDeserialize(as=ArrayList.class, contentAs=ListItem.class)
    public List<ListItem> object;  

}

Ваш ответ на вопрос