简体   繁体   English

使用Jackson将Java列表序列化为XML和JSON

[英]Serialize Java List of Lists to XML and JSON using Jackson

In my Restlet based APIs, using the Restlet Jackson extension, I am attempting to serialize a Java object to both XML and JSON, and am unable to get the formats that I expect (that the existing API already publishes) with a nested list or multidimensional array. 在我的基于Restlet的API中,使用Restlet Jackson扩展,我试图将Java对象序列化为XML和JSON,但是无法使用嵌套列表或多维数据集获取我期望的格式(现有API已经发布)。阵列。

Here is my POJO that generates the correct JSON: 这是我的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 of TableResponse, I would like to see JSON like this: TableResponse的JSON,我希望看到这样的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"
  ]
}

And I would expect to be able to make XML like this: 我希望能够使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>

But instead I get this XML (XML of TableResponse), which loses a dimension: 但是我得到的是这个XML(TableResponse的XML),它失去了一个维度:

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

Using an alternate POJO structure, I can acheive the XML I expect for a nested list (it is a pain to initialize the data and instantiate the classes for this structure) but then the JSON is not what I want: 通过使用备用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 of TableResponseForXML (Objects are wrapping the inner lists): TableResponseForXML的JSON(对象包装内部列表):

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

Some of the dependencies in my project are: 我的项目中的一些依赖项是:

  • com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.5.3 com.fasterxml.jackson.dataformat:杰克逊 - DATAFORMAT-XML:2.5.3
  • org.restlet.jee:org.restlet:2.3.5 org.restlet.jee:org.restlet:2.3.5
  • org.restlet.jee:org.restlet.ext.jackson:2.3.5 org.restlet.jee:org.restlet.ext.jackson:2.3.5
  • org.restlet.jee:org.restlet.ext.json:2.3.5 org.restlet.jee:org.restlet.ext.json:2.3.5

Is there a way to make nested lists work the way I expected between JSON and XML with the first POJO structure? 有没有一种方法可以使嵌套列表按我期望的第一个POJO结构在JSON和XML之间工作? The multi-dimensional JSON Array is simpler to work with than an object wrapping each list, and is the existing published spec for this API. 多维JSON数组比包装每个列表的对象更易于使用,并且是此API的现有已发布规范。

A side note, I also tried the suggestion in Jackson: different XML and JSON format , but failed to get my XmlAdapter/@XmlJavaTypeAdapter to be used here with restlet. 附带说明一下,我还尝试了Jackson的建议:不同的XML和JSON格式 ,但是未能将我的XmlAdapter / @ XmlJavaTypeAdapter与restlet一起使用。

It seems difficult to handle these both formats only using Jackson annotations. 仅使用Jackson注释似乎很难处理这两种格式。 For your use case, I think that you need to implement a custom serializer that handles separately JSON and XML. 对于您的用例,我认为您需要实现一个自定义的序列化器,该序列化器分别处理JSON和XML。

This serializer looks like the following: 该序列化器如下所示:

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

The serializeJson method will build the JSON content: 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();
}

and the serializeXml one, the XML one: 和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();
}

The last step consists in configuring the serializer on the ObjectMapper instance that is used by the Restlet Jackson converter. 最后一步是在Restlet Jackson转换器使用的ObjectMapper实例上配置序列化程序。 For this you need to extend both JacksonConverter and JacksonRepresentation classes. 为此,您需要扩展JacksonConverterJacksonRepresentation类。

First the JacksonRepresentation class where you override the getObjectMapper method to register your serialiser for the TableResponse class: 首先,在JacksonRepresentation类中,您重写getObjectMapper方法以为TableResponse类注册序列化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;
    }
}

Then the CustomJacksonConverter class that will use this kind of representation when necessary: 然后,在必要时将使用这种表示形式的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);
    }
}

To register the CustomJacksonConverter class, you can rely on the getRegisteredConverters of the Engine before starting your component. 要注册CustomJacksonConverter类,你可以依靠getRegisteredConverters中的Engine开始你的组件之前。 Don't forget to remove the default JacksonConverter . 不要忘记删除默认的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());
}

This way you will have the output content you want for both XML and JSON based on the content negotiation ( Accept header). 这样,您将基于内容协商获得所需的XML和JSON输出内容( Accept标头)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM