无法使用Jersey客户端解组JSON对象数组

我试图解组的单元素JSON数组:

[ { "id":"42", "status":"Active", "name":"purple monkey dishwasher" } ] 

相应的Java类(为简洁省略了getter和setter):

 @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Badge { @XmlElement(name="id") private String id; @XmlElement(name="status") private Status status; @XmlElement(name="name") private String name; public static enum Status { Active, NotActive } } 

Jersey客户端代码发出HTTP请求并且应该将上述JSON解组为单元素List

 Client client = Client.create(); WebResource apiRoot = client.resource("http://localhost:9000/api"); List badges = apiRoot.path("/badges").get(new GenericType<List>(){}); 

最后一行,特别是WebResource#get()调用,抛出以下exception:

 javax.xml.bind.UnmarshalException: unexpected element (uri:"", local:"status"). Expected elements are  at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallingContext.handleEvent(UnmarshallingContext.java:662) at com.sun.xml.bind.v2.runtime.unmarshaller.Loader.reportError(Loader.java:258) at com.sun.xml.bind.v2.runtime.unmarshaller.Loader.reportError(Loader.java:253) at com.sun.xml.bind.v2.runtime.unmarshaller.Loader.reportUnexpectedChildElement(Loader.java:120) at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallingContext$DefaultRootLoader.childElement(UnmarshallingContext.java:1063) at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallingContext._startElement(UnmarshallingContext.java:498) at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallingContext.startElement(UnmarshallingContext.java:480) at com.sun.xml.bind.v2.runtime.unmarshaller.InterningXmlVisitor.startElement(InterningXmlVisitor.java:75) at com.sun.xml.bind.v2.runtime.unmarshaller.StAXStreamConnector.handleStartElement(StAXStreamConnector.java:247) at com.sun.xml.bind.v2.runtime.unmarshaller.StAXStreamConnector.bridge(StAXStreamConnector.java:181) at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal0(UnmarshallerImpl.java:369) at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal(UnmarshallerImpl.java:341) at com.sun.jersey.core.provider.jaxb.AbstractListElementProvider.readFrom(AbstractListElementProvider.java:232) at com.sun.jersey.api.client.ClientResponse.getEntity(ClientResponse.java:552) at com.sun.jersey.api.client.ClientResponse.getEntity(ClientResponse.java:522) at com.sun.jersey.api.client.WebResource.handle(WebResource.java:617) at com.sun.jersey.api.client.WebResource.get(WebResource.java:191) at com.redacted.badge.client.BadgerImpl.findAllBadges(BadgerImpl.java:105) at com.redacted.webapp.admin.BadgeAction.unspecified(BadgeAction.java:40) at org.apache.struts.actions.DispatchAction.dispatchMethod(DispatchAction.java:245) at org.apache.struts.actions.DispatchAction.execute(DispatchAction.java:170) at org.apache.struts.chain.commands.servlet.ExecuteAction.execute(ExecuteAction.java:58) at org.apache.struts.chain.commands.AbstractExecuteAction.execute(AbstractExecuteAction.java:67) at org.apache.struts.chain.commands.ActionCommandBase.execute(ActionCommandBase.java:51) at org.apache.commons.chain.impl.ChainBase.execute(ChainBase.java:190) at org.apache.commons.chain.generic.LookupCommand.execute(LookupCommand.java:304) at org.apache.commons.chain.impl.ChainBase.execute(ChainBase.java:190) at org.apache.struts.chain.ComposableRequestProcessor.process(ComposableRequestProcessor.java:283) at org.apache.struts.action.ActionServlet.process(ActionServlet.java:1913) at org.apache.struts.action.ActionServlet.doGet(ActionServlet.java:449) at javax.servlet.http.HttpServlet.service(HttpServlet.java:617) at javax.servlet.http.HttpServlet.service(HttpServlet.java:717) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) at com.opensymphony.module.sitemesh.filter.PageFilter.parsePage(PageFilter.java:119) at com.opensymphony.module.sitemesh.filter.PageFilter.doFilter(PageFilter.java:55) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) at com.redacted.webapp.filter.MemberFilter.doFilter(MemberFilter.java:83) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) at com.redacted.webapp.filter.AuthFilter.doFilter(AuthFilter.java:113) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) at org.displaytag.filter.ResponseOverrideFilter.doFilter(ResponseOverrideFilter.java:125) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) at com.redacted.webapp.filter.LanguageHandlingFilter.doFilter(LanguageHandlingFilter.java:151) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) at com.redacted.webapp.filter.SetCharacterEncodingFilter.doFilter(SetCharacterEncodingFilter.java:146) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) at com.redacted.webapp.filter.PartnerFilter.doFilter(PartnerFilter.java:59) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) at com.redacted.webapp.filter.SessionStatusFilter.doFilter(SessionStatusFilter.java:113) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:470) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127) at com.googlecode.psiprobe.Tomcat60AgentValve.invoke(Tomcat60AgentValve.java:30) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:298) at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:859) at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:588) at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:489) at java.lang.Thread.run(Thread.java:680) 

我在Badge上尝试了各种注释组合,或者使用数组而不是GenericType

 List badges = Arrays.asList(apiRoot.path("/badges").get(Badge[].class)); 

或使用中间ClientResponse

 GenericType<List> type = new GenericType<List>(){}; ClientResponse clientResponse = apiRoot.path("/badges").get(ClientResponse.class); List badges = clientResponse.getEntity(type); 

但到目前为止都没有解决问题。

更令人困惑的是,我现有的设置在解组JSON编码的Badge 没有问题 ,这些Badge位于其他结构中,如下所示:

 { "userid":"123456789", "userbadges":[ { "badge":{ "id":"42", "status":"Active", "name":"purple monkey dishwasher" }, "earned":"2012-03-06 18:16:18.172" } ] } 

我究竟做错了什么?

通过使用JacksonJsonProvider作为Jersey Client实例的MessageBody(Reader|Writer)提供程序,我能够以最小的努力解决这个问题:

 ClientConfig cfg = new DefaultClientConfig(); cfg.getClasses().add(JacksonJsonProvider.class); Client client = Client.create(cfg); 

jackson的MessageBodyReader实现似乎比Jersey JSON更好。

感谢我如何自定义JAXB对象列表到JSON的序列化? 指出我在jackson的方向。

注意:我是EclipseLink JAXB(MOXy)的负责人,也是JAXB(JSR-222)专家组的成员。

您可以使用添加到EclipseLink 2.4中的MOXy组件的JSON Binding扩展来处理此用例:

演示

Jersey客户端API允许您从客户端的服务器端利用相同的MessageBodyReader / MessageBodyWriter

 package forum9627170; import java.util.List; import org.example.Customer; import com.sun.jersey.api.client.*; import com.sun.jersey.api.client.config.*; public class Demo { public static void main(String[] args) { ClientConfig cc = new DefaultClientConfig(); cc.getClasses().add(MOXyJSONProvider.class); Client client = Client.create(cc); WebResource apiRoot = client.resource("http://localhost:9000/api"); List badges = apiRoot.path("/badges").accept("application/json").get(new GenericType>(){}); for(Badge badge : badges) { System.out.println(badge.getId()); } } } 

MOXyJSONProvider

下面是一个通用的MessageBodyReader / MessageBodyWriter ,它可以与任何服务器/客户端一起使用,以启用MOXy作为JSON绑定提供程序。

 package forum9627170; import java.io.*; import java.lang.annotation.Annotation; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import javax.xml.transform.stream.StreamSource; import javax.ws.rs.*; import javax.ws.rs.core.*; import javax.ws.rs.ext.*; import javax.xml.bind.*; @Provider @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public class MOXyJSONProvider implements MessageBodyReader, MessageBodyWriter{ @Context protected Providers providers; public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { return true; } public Object readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, InputStream entityStream) throws IOException, WebApplicationException { try { Class domainClass = getDomainClass(genericType); Unmarshaller u = getJAXBContext(domainClass, mediaType).createUnmarshaller(); u.setProperty("eclipselink.media-type", mediaType.toString()); u.setProperty("eclipselink.json.include-root", false); return u.unmarshal(new StreamSource(entityStream), domainClass).getValue(); } catch(JAXBException jaxbException) { throw new WebApplicationException(jaxbException); } } public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { return true; } public void writeTo(Object object, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException { try { Marshaller m = getJAXBContext(getDomainClass(genericType), mediaType).createMarshaller(); m.setProperty("eclipselink.media-type", mediaType.toString()); m.setProperty("eclipselink.json.include-root", false); m.marshal(object, entityStream); } catch(JAXBException jaxbException) { throw new WebApplicationException(jaxbException); } } public long getSize(Object t, Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { return -1; } private JAXBContext getJAXBContext(Class type, MediaType mediaType) throws JAXBException { ContextResolver resolver = providers.getContextResolver(JAXBContext.class, mediaType); JAXBContext jaxbContext; if(null == resolver || null == (jaxbContext = resolver.getContext(type))) { return JAXBContext.newInstance(type); } else { return jaxbContext; } } private Class getDomainClass(Type genericType) { if(genericType instanceof Class) { return (Class) genericType; } else if(genericType instanceof ParameterizedType) { return (Class) ((ParameterizedType) genericType).getActualTypeArguments()[0]; } else { return null; } } } 

了解更多信息

  • MOXy作为您的JAX-RS JSON提供者 – 客户端
  • MOXy作为您的JAX-RS JSON提供程序 – 服务器端
  • 将EclipseLink MOXy指定为JAXB提供程序

UPDATE

在GlassFish 4中,EclipseLink JAXB(MOXy)是Jersey使用的默认JSON绑定提供程序:

默认情况下,Jersey使用JAXB进行(un)编组过程,遗憾的是,JAXB JSON处理器不是标准处理器( 忽略单元素数组,将空数组转换为单元素空数组…… )。

所以,你有两个选择:

  1. 将JAXB配置为更标准(有关更多信息, 请参见此处 );
  2. 用Jackson而不是JAXB – 我推荐。

使用Jackson客户端是通过以下方式完成的:

 ClientConfig clientConfig = new DefaultClientConfig(); clientConfig.getFeatures().put(JSONConfiguration.FEATURE_POJO_MAPPING, Boolean.TRUE); Client client = Client.create(clientConfig); List badges = client.resource("/badges").getEntity(new GenericType>() {}); 

我有类似的问题,并通过以下解决

  1. 像这样制作一个JAXB上下文解析器

     import java.util.ArrayList; import java.util.List; import javax.ws.rs.ext.ContextResolver; import javax.ws.rs.ext.Provider; import javax.xml.bind.JAXBContext; import com.sun.jersey.api.json.JSONConfiguration; import com.sun.jersey.api.json.JSONJAXBContext; @Provider public class JAXBContextResolver implements ContextResolver { private JAXBContext context; private Class[] types = { Badge.class }; private List> classes = new ArrayList>(); public JAXBContextResolver() throws Exception { this.context = new JSONJAXBContext(JSONConfiguration.natural().build(), types); for (Class clazz : types) { classes.add(clazz); } } public JAXBContext getContext(Class objectType) { return classes.contains(objectType) ? context : null; } } 
  2. 为您的客户添加了上下文解析器

     ClientConfig config = new DefaultClientConfig(); config.getClasses().add(JAXBContextResolver.class); Client client = Client.create(config); 
  3. 现在您可以获取对象数组

     WebResource apiRoot = client.resource("http://localhost:9000/api"); Badge[] badges = apiRoot.path("/badges").get(Badge[].class); 

如果你想要一个清单,只需使用

  Arrays.asList(badges) 

导入这个

  com.sun.jersey jersey-json 1.17 compile  

这是unmarshall的代码

 import com.sun.jersey.api.json.JSONJAXBContext; import com.sun.jersey.api.json.JSONUnmarshaller; public static  T unmarshalJson(String jsonTxt, Class clazz) throws JAXBException { JSONJAXBContext jctx = new JSONJAXBContext(clazz); JSONUnmarshaller unm = jctx.createJSONUnmarshaller(); return (T)unm.unmarshalFromJSON(new StringReader(jsonTxt), clazz); } 

这可能与生产者没有将单个列表正确编码为JSON的问题有关。 请参阅此文章以获得更全面的解释和建议的解决方案。

根据文章描述的内容,以及错误消息,我猜测正在生成以下内容:

 { { "id":"42", "status":"Active", "name":"purple monkey dishwasher" } } 

根据该文章,解决方案在于扩展和自定义提供程序以更正单个列表和空列表如何格式化为JSON。

不幸的是,这篇文章是用德语写的,我必须自己翻译一下 – 让我知道它是否真的无法解决你的问题。 如果是这样的话,可以归功于该文章的作者Dirk Dittmar。

PS – 如果你像我一样使用Chrome翻译页面,请确保切换回原始版本以查看代码片段,因为它们的一部分被错误地“翻译”为空白。