我该如何自定义将JAXB对象列表序列化为JSON的过程?

55

我正在使用Jersey为服务器组件创建REST Web服务。

我想要在列表中序列化的JAXB注释对象如下:

@XmlRootElement(name = "distribution")
@XmlType(name = "tDistribution", propOrder = {
    "id", "name"
})
public class XMLDistribution {
    private String id;
    private String name;
    // no-args constructor, getters, setters, etc
}

我有一个REST资源,用于检索一个分发,它看起来像这样:

@Path("/distribution/{id: [1-9][0-9]*}")
public class RESTDistribution {
    @GET
    @Produces("application/json")
    public XMLDistribution retrieve(@PathParam("id") String id) {
        return retrieveDistribution(Long.parseLong(id));
    }
    // business logic (retrieveDistribution(long))
}

我还有一个用于检索所有分发列表的REST资源,它看起来像这样:

@Path("/distributions")
public class RESTDistributions {
    @GET
    @Produces("application/json")
    public List<XMLDistribution> retrieveAll() {
        return retrieveDistributions();
    }
    // business logic (retrieveDistributions())
}

我使用ContextResolver来定制JAXB序列化,目前已配置如下:

@Provider
@Produces("application/json")
public class JAXBJSONContextResolver implements ContextResolver<JAXBContext> {
    private JAXBContext context;
    public JAXBJSONContextResolver() throws Exception {
        JSONConfiguration.MappedBuilder b = JSONConfiguration.mapped();
        b.nonStrings("id");
        b.rootUnwrapping(true);
        b.arrays("distribution");
        context = new JSONJAXBContext(b.build(), XMLDistribution.class);
    }
    @Override
    public JAXBContext getContext(Class<?> objectType) {
        return context;
    }
}

REST资源和上下文解析器都能正常工作。这是第一个资源的输出示例:

// path: /distribution/1
{
  "id": 1,
  "name": "Example Distribution"
}

这正是我想要的。以下是该列表的输出示例:

// path: /distributions
{
  "distribution": [{
    "id": 1,
    "name": "Sample Distribution 1"
  }, {
    "id": 2,
    "name": "Sample Distribution 2"
  }]
}

这不是我想要的。

我不明白为什么有一个封闭的distribution标签在那里。我想用上下文解析器中的.rootUnwrapping(true)将其删除,但显然这只会删除另一个封闭标签。这是使用.rootUnwrapping(false)的输出:

// path: /distribution/1
{
  "distribution": {
    "id": 1,
    "name": "Example Distribution"
  }
} // not ok
// path: /distributions
{
  "xMLDistributions": {
    "distribution": [{
      "id": 1,
      "name": "Sample Distribution 1"
    }, {
      "id": 2,
      "name": "Sample Distribution 2"
    }]
  }
}

我还需要配置.arrays("distribution"),以便始终获取JSON数组,即使只有一个元素。

理想情况下,我希望输出如下:

// path: /distribution/1
{
  "id": 1,
  "name": "Example Distribution"
} // currently works
// path: /distributions
[{
  "id": 1,
  "name": "Sample Distribution 1"
}, {
  "id": 2,
  "name": "Sample Distribution 2"
}]
我尝试返回一个List<XMLDistribution>,一个XMLDistributionList(包装了一个列表),一个XMLDistribution[],但我找不到一种方法来获取符合我所需格式的简单JSON数组。
我还尝试了JSONConfiguration.natural()JSONConfiguration.mappedJettison()等返回的其他符号,但没有得到类似我需要的内容。
有人知道是否可以配置JAXB来实现这个吗?
2个回答

105
我找到了一个解决方案:将JAXB JSON序列化器替换为行为更好的JSON序列化器,如Jackson。简单的方法是使用jackson-jaxrs,它已经为您完成了这项工作。该类是JacksonJsonProvider。您所需要做的就是编辑项目的web.xml文件,以便Jersey(或其他JAX-RS实现)扫描它。以下是您需要添加的内容:
<init-param>
  <param-name>com.sun.jersey.config.property.packages</param-name>
  <param-value>your.project.packages;org.codehaus.jackson.jaxrs</param-value>
</init-param>
那就这样了。Jackson将被用于JSON序列化,而对于列表和数组,它按你预期的方式工作。
写你自己的自定义MessageBodyWriter以生成"application/json"也是一种更长的方式。以下是一个示例:
@Provider @Produces("application/json") public class JsonMessageBodyWriter implements MessageBodyWriter { @Override public long getSize(Object obj, Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { return -1; }
@Override public boolean isWriteable(Class type, Type genericType, Annotation annotations[], MediaType mediaType) { return true; }
@Override public void writeTo(Object target, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, OutputStream outputStream) throws IOException { new ObjectMapper().writeValue(outputStream, target); } }
你需要确保你的web.xml包含该包,就像上面的现成的解决方案一样。
无论哪种方式:哇!你会看到格式正确的JSON。
你可以从这里下载Jackson: http://jackson.codehaus.org/

Maven依赖项“jersey-json”包含正确的jackson json转换器。 <dependency> <groupId>com.sun.jersey</groupId> <artifactId>jersey-json</artifactId> <version>1.13</version> </dependency> - OneWorld
好的回答!然而,我觉得默认的JSON转换器不会被修复很奇怪。我敢打赌这是因为:无用 < 臃肿 < J2EE。 - mschmoock
如果在切换到Jackson后,您的数组仍然被包装,请查看https://dev59.com/omTWa4cB1Zd3GeqPIfUF#12875847。 - Ashley Ross
1
我正在使用Guice而不是web.xml进行配置;这篇博客帖子对我很有帮助:http://www.flamingpenguin.co.uk/blog/2012/08/17/use-new-version-of-jackson-json-with-jersey/ - MrDrews
+1...我只想指出,在我的本地开发中,“init-param”解决方案在Jetty中有效,但在部署到Websphere时无效。然而,你的第二个解决方案很好用。 - limc
我认为我们还需要将jackson-core-asl-1.5.3.jar和/或jackson-mapper-asl-1.5.3.jar包含到我们的类路径中。我尝试仅放置jackson-jaxrs-1.5.3.jar,但它没有起作用。 - Pahlevi Fikri Auliya

13

Jonhatan的回答非常出色,对我非常有用。

只是一个更新:

如果您使用Jackson的2.x版本(例如2.1版本),则类为com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider,因此web.xml应为:

<init-param>
  <param-name>com.sun.jersey.config.property.packages</param-name>
  <param-value>your.project.packages;com.fasterxml.jackson.jaxrs.json</param-value>
</init-param>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接