【问题标题】:Serialize Java List of Lists to XML and JSON using Jackson使用 Jackson 将 Java 列表序列化为 XML 和 JSON
【发布时间】:2016-06-14 14:35:33
【问题描述】:

在我的基于 Restlet 的 API 中,使用 Restlet Jackson 扩展,我尝试将 Java 对象序列化为 XML 和 JSON,但无法通过嵌套获取我期望的格式(现有 API 已经发布)列表或多维数组。

这是生成正确 JSON 的 POJO:

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

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

我希望能够像这样制作 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(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>

使用另一种 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;
    }
  }
}

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

我项目中的一些依赖是:

  • com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.5.3
  • org.restlet.jee:org.restlet:2.3.5
  • org.restlet.jee:org.restlet.ext.jackson:2.3.5
  • org.restlet.jee:org.restlet.ext.json:2.3.5

有没有办法让嵌套列表按照我期望的方式在 JSON 和 XML 之间使用第一个 POJO 结构工作?与包装每个列表的对象相比,多维 JSON 数组更易于使用,并且是此 API 的现有已发布规范。

附带说明,我也尝试了Jackson: different XML and JSON format 中的建议,但未能让我的 XmlAdapter/@XmlJavaTypeAdapter 在这里与 restlet 一起使用。

【问题讨论】:

  • 我习惯于使用 GSON 来创建 JSON 字符串,所以我只能谈一谈。根据我的经验,我必须对各种数据类型进行大量试验才能获得我想要的结果,并发现 HashMaps 或 LinkedHashMaps 以及 ArrayList 的组合效果最好。您可能需要对这些进行试验才能看到 jackson 的结果。

标签: java json xml jackson restlet


【解决方案1】:

仅使用 Jackson 注释似乎很难处理这两种格式。对于您的用例,我认为您需要实现一个自定义序列化程序,分别处理 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();
}

最后一步是在 Restlet Jackson 转换器使用的 ObjectMapper 实例上配置序列化程序。为此,您需要同时扩展 JacksonConverterJacksonRepresentation 类。

首先是 JacksonRepresentation 类,您可以在其中覆盖 getObjectMapper 方法来为 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 类,您可以在启动组件之前依赖EnginegetRegisteredConverters。不要忘记删除默认的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());
}

这样,您将根据内容协商(Accept 标头)获得 XML 和 JSON 所需的输出内容。

【讨论】:

  • 您好 Thierry,请问如何强制 Restlet 使用 XStream 而不是 Jackson?
猜你喜欢
  • 2021-12-10
  • 1970-01-01
  • 2018-07-24
  • 1970-01-01
  • 2012-04-06
  • 1970-01-01
  • 1970-01-01
  • 2018-07-18
  • 1970-01-01
相关资源
最近更新 更多