2016-03-01 1 views
1

В моих API-интерфейсах на основе Restlet, используя расширение Restlet Jackson, я пытаюсь сериализовать объект Java как для XML, так и для JSON, и не могу получить ожидаемые форматы (уже существующий API уже публикует) с вложенным списком или многомерным массивом.Сериализация Java Список списков для XML и JSON с использованием Jackson

Вот мой POJO, который генерирует правильный JSON:

@JacksonXmlRootElement(localName = "table") 
@JsonInclude(JsonInclude.Include.NON_NULL) 
public class TableResponse { 

    protected List data; 
    protected String[] columns; 

    public TableResponse(String[] columns, List<List<String>> data) { 
    this.columns = columns; 
    this.data = data; 
    } 

    @JacksonXmlElementWrapper(localName = "data") 
    @JacksonXmlProperty(localName = "row") 
//@CanIAddSomeAnnotationHereForNestedListElements? 
    public List<List<String>> getData() { 
    return data; 
    } 

    @JacksonXmlElementWrapper(localName = "columns") 
    @JacksonXmlProperty(localName = "column") 
    public String[] getColumns() { 
    return columns; 
    } 
} 

JSON из TableResponse, я хотел бы видеть JSON как это:

{ 
    "data": [ 
    [ 
     "Row 1 Cell A", 
     "Row 1 Cell B" 
    ], 
    [ 
     "Row 2 Cell A", 
     "Row 2 Cell B" 
    ], 
    [ 
     "Row 3 Cell A", 
     "Row 3 Cell B" 
    ] 
    ], 
    "columns": [ 
    "Column 1", 
    "Column 2" 
    ] 
} 

И я бы ожидать, чтобы быть в состоянии сделать XML, как это:

<table> 
    <data> 
     <row> 
      <value>Row 1 Cell A</value> 
      <value>Row 1 Cell B</value> 
     </row> 
     <row> 
      <value>Row 2 Cell A</value> 
      <value>Row 2 Cell B</value> 
     </row> 
     <row> 
      <value>Row 3 Cell A</value> 
      <value>Row 3 Cell B</value> 
     </row> 
    </data> 
    <columns> 
     <column>Column 1</column> 
     <column>Column 2</column> 
    </columns> 
</table> 

Но вместо этого я получаю этот XML (XML из TableResponse), который теряет измерение:

<table> 
    <data> 
     <row>Row 1 Cell A</row> 
     <row>Row 1 Cell B</row> 
     <row>Row 2 Cell A</row> 
     <row>Row 2 Cell B</row> 
     <row>Row 3 Cell A</row> 
     <row>Row 3 Cell B</row> 
    </data> 
    <columns> 
     <column>Column 1</column> 
     <column>Column 2</column> 
    </columns> 
</table> 

Используя альтернативную структуру POJO, я могу получение идеальной XML-я ожидать для вложенного списка (это боль, чтобы инициализировать данные и создавать экземпляры классов для этой структуры), а затем JSON не то, что я хочу :

@JacksonXmlRootElement(localName = "table") 
@JsonInclude(JsonInclude.Include.NON_NULL) 
public class TableResponseForXML { 

    protected List data; 
    protected String[] columns; 

    public TableResponseForXML(String[] columns, List<Row> data) { 
    this.columns = columns; 
    this.data = data; 
    } 


    @JacksonXmlElementWrapper(localName = "data") 
    @JacksonXmlProperty(localName = "row") 
    public List<Row> getData() { 
    return data; 
    } 

    @JacksonXmlElementWrapper(localName = "columns") 
    @JacksonXmlProperty(localName = "column") 
    public String[] getColumns() { 
    return columns; 
    } 


    public static class Row { 
    private List<Value> values; 

    public Row(List<Value> values) { 
     this.values = values; 
    } 

    @JacksonXmlElementWrapper(localName = "row", useWrapping = false) 
    @JacksonXmlProperty(localName = "value") 
    public List<Value> getValues() { 
     return values; 
    } 
    } 


    public static class Value { 
    private String value; 

    public Value(String value) { 
     this.value = value; 
    } 

    @JsonValue 
    public String getValue() { 
     return value; 
    } 
    } 
} 

JSON из TableResponseForXML (объекты оборачивают внутренние списки):

{ 
    "data": [ 
    { 
     "values": [ 
     "Row 1 Cell A", 
     "Row 1 Cell B" 
     ] 
    }, 
    { 
     "values": [ 
     "Row 2 Cell A", 
     "Row 2 Cell B" 
     ] 
    }, 
    { 
     "values": [ 
     "Row 3 Cell A", 
     "Row 3 Cell B" 
     ] 
    } 
    ], 
    "columns": [ 
    "Column 1", 
    "Column 2" 
    ] 
} 

Некоторые из зависимостей в моем проекте являются:

  • com.fasterxml.jackson.dataformat: джексон-DataFormat-XML: 2.5.3
  • org.restlet.jee: org.restlet: 2.3.5
  • org.restlet.jee: орг. restlet.ext.jackson: 2.3.5
  • org.restlet.jee: org.restlet.ext.json: 2.3.5

есть ли способ, чтобы сделать вложенные списки работать так, как я ожидал от JSON и XML с первой структурой POJO? Многомерный JSON Array проще работать, чем объект, обертывающий каждый список, и является существующей опубликованной спецификацией для этого API.

Заметка стороны, я также попробовал предложение в Jackson: different XML and JSON format, но не смог получить мой XmlAdapter/@ XmlJavaTypeAdapter, который будет использоваться здесь с помощью рестарта.

+0

Я привык общаться с GSON для создания строк JSON, поэтому я могу только говорить об этом. По моему опыту, мне пришлось много экспериментировать с различными типами данных, чтобы получить то, что я хотел, и нашел, что HashMaps или LinkedHashMaps вместе с комбинацией ArrayList работал лучше всего. Возможно, вам придется поэкспериментировать с ними, чтобы увидеть результат от джексона. – Alan

ответ

2

Похоже, что эти форматы обрабатываются только с использованием аннотаций Джексона. Для вашего случая использования, я думаю, вам нужно реализовать собственный сериализатор, который обрабатывает отдельно JSON и XML.

Это сериализатору выглядит следующим образом:

public class TableResponseSerializer extends StdSerializer<TableResponse> { 
    private MediaType mediaType; 

    public TableResponseSerializer(MediaType mediaType) { 
     super(TableResponse.class); 
     this.mediaType = mediaType; 
    } 

    private void serializeJson(TableResponse swe, 
      JsonGenerator jgen, 
      SerializerProvider sp) throws IOException, JsonGenerationException { 
     (...) 
    } 

    private void serializeXml(TableResponse swe, 
      JsonGenerator jgen, 
      SerializerProvider sp) throws IOException, JsonGenerationException { 
     (...)   
    } 

    @Override 
    public void serialize(TableResponse swe, 
          JsonGenerator jgen, 
          SerializerProvider sp) throws IOException, JsonGenerationException { 
     if (mediaType.equals(MediaType.APPLICATION_JSON)) { 
      serializeJson(swe, jgen, sp); 
     } else if (mediaType.equals(MediaType.TEXT_XML)) { 
      serializeXml(swe, jgen, sp); 
     } 
    } 
} 

Метод serializeJson будет создавать контент в формате JSON:

private void serializeJson(TableResponse swe, 
     JsonGenerator jgen, 
     SerializerProvider sp) throws IOException, JsonGenerationException { 
    jgen.writeStartObject();  

    // Data 
    jgen.writeArrayFieldStart("data"); 
    for (List<String> row : swe.getData()) { 
     jgen.writeStartArray(); 
     for (String rowElt : row) { 
      jgen.writeString(rowElt); 
     } 
     jgen.writeEndArray(); 
    } 
    jgen.writeEndArray(); 

    // Columns 
    jgen.writeArrayFieldStart("columns"); 
    for (String column : swe.getColumns()) { 
     jgen.writeString(column); 
    } 
    jgen.writeEndArray(); 

    jgen.writeEndObject(); 
} 

и serializeXml один, то XML одно:

private void serializeXml(TableResponse swe, 
     JsonGenerator jgen, 
     SerializerProvider sp) throws IOException, JsonGenerationException { 

    jgen.writeStartObject();  

    // Data 
    jgen.writeObjectFieldStart("data"); 
    jgen.writeArrayFieldStart("row"); 
    for (List<String> row : swe.getData()) { 
     jgen.writeStartObject(); 
     jgen.writeArrayFieldStart("value"); 
     for (String rowElt : row) { 
      jgen.writeString(rowElt); 
     } 
     jgen.writeEndArray(); 
     jgen.writeEndObject(); 
    } 
    jgen.writeEndArray(); 
    jgen.writeEndObject(); 

    // Columns 
    jgen.writeObjectFieldStart("columns"); 
    jgen.writeArrayFieldStart("column"); 
    for (String column : swe.getColumns()) { 
     jgen.writeString(column); 
    } 
    jgen.writeEndArray(); 
    jgen.writeEndObject(); 

    jgen.writeEndObject(); 
} 

Последний шаг состоит в настройке сериализатора на ObjectMapper instanc e, который используется преобразователем Restlet Jackson. Для этого вам необходимо расширить классы JacksonConverter и JacksonRepresentation.

Сначала JacksonRepresentation класса, где вы переопределять метод getObjectMapper зарегистрировать свой serialiser для TableResponse класса:

public class CustomJacksonRepresentation<T> extends JacksonRepresentation<T> { 
    public CustomJacksonRepresentation(MediaType mediaType, T object) { 
     super(mediaType, object); 
    } 

    public CustomJacksonRepresentation(Representation representation, 
         Class<T> objectClass) { 
     super(representation, objectClass); 
    } 

    public CustomJacksonRepresentation(T object) { 
     super(object); 
    } 

    @Override 
    protected ObjectMapper createObjectMapper() { 
     ObjectMapper objectMapper = super.createObjectMapper(); 

     if (getObjectClass().equals(TableResponse.class)) { 
      SimpleModule mod = new SimpleModule("");  
      mod.addSerializer(new TableResponseSerializer(getMediaType())); 
      objectMapper.registerModule(mod); 
     } 

     return objectMapper; 
    } 
} 

Тогда CustomJacksonConverter класса, который будет использовать этот вид представления, когда это необходимо:

public class CustomJacksonConverter extends JacksonConverter { 
    protected <T> JacksonRepresentation<T> create(MediaType mediaType, T source) { 
     return new CustomJacksonRepresentation<T>(mediaType, source); 
    } 

    protected <T> JacksonRepresentation<T> create(Representation source, 
      Class<T> objectClass) { 
     return new CustomJacksonRepresentation<T>(source, objectClass); 
    } 
} 

Чтобы зарегистрировать класс CustomJacksonConverter, перед запуском компонента вы можете положиться на getRegisteredConvertersEngine. Не забудьте удалить по умолчанию JacksonConverter.

List<ConverterHelper> converters = Engine.getInstance().getRegisteredConverters(); 
JacksonConverter jacksonConverter = new JacksonConverter(); 
for (ConverterHelper converter : converters) { 
    if (converter instanceof JacksonConverter) { 
     jacksonConverter = (JacksonConverter) converter; 
     break; 
    } 
} 

if (jacksonConverter!=null) { 
    converters.remove(jacksonConverter); 
    converters.add(new CustomJacksonConverter()); 
} 

Таким образом, вы будете иметь содержание вывода вы хотите как для XML и JSON на основе переговоров контента (Accept заголовка).

Смежные вопросы