无法使用 Jersey 客户端解组 JSON 对象数组
Posted
技术标签:
【中文标题】无法使用 Jersey 客户端解组 JSON 对象数组【英文标题】:Cannot unmarshal a JSON array of objects using Jersey Client 【发布时间】:2012-03-26 11:56:15 【问题描述】:我正在尝试解组的单元素 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
发出 HTTP 请求的 Jersey 客户端代码应该将上述 JSON 解组为单元素 List<Foo>
:
Client client = Client.create();
WebResource apiRoot = client.resource("http://localhost:9000/api");
List<Badge> badges = apiRoot.path("/badges").get(new GenericType<List<Badge>>());
最后一行,特别是WebResource#get()
调用,抛出以下异常:
javax.xml.bind.UnmarshalException: unexpected element (uri:"", local:"status"). Expected elements are <badge>
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<Badge> badges = Arrays.asList(apiRoot.path("/badges").get(Badge[].class));
或使用中间ClientResponse
:
GenericType<List<Badge>> type = new GenericType<List<Badge>>();
ClientResponse clientResponse = apiRoot.path("/badges").get(ClientResponse.class);
List<Badge> badges = clientResponse.getEntity(type);
但目前还没有解决问题。
更令人困惑的是,我现有的设置没有问题解组其他结构内的 JSON 编码 Badge
s,如下所示:
"userid":"123456789",
"userbadges":[
"badge":
"id":"42",
"status":"Active",
"name":"purple monkey dishwasher"
,
"earned":"2012-03-06 18:16:18.172"
]
我做错了什么?
【问题讨论】:
您是否尝试将状态枚举类型提取到它自己的类中并在该类中添加@XmlEnum
注释?
状态字段不是问题,不是远程的。
【参考方案1】:
通过使用 JacksonJsonProvider
作为 Jersey 客户端实例的 MessageBody(Reader|Writer)
提供程序,我能够以最小的努力解决这个问题:
ClientConfig cfg = new DefaultClientConfig();
cfg.getClasses().add(JacksonJsonProvider.class);
Client client = Client.create(cfg);
Jackson 的 MessageBodyReader
实现似乎比 Jersey JSON 实现的更好。
感谢How can I customize serialization of a list of JAXB objects to JSON? 为我指明杰克逊方向。
【讨论】:
这种方式没有帮助。但是new DefaultClientConfig(JacksonJaxbJsonProvider.class)
可以。【参考方案2】:
注意:我是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<Badge> badges = apiRoot.path("/badges").accept("application/json").get(new GenericType<List<Badge>>());
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<Object>, MessageBodyWriter<Object>
@Context
protected Providers providers;
public boolean isReadable(Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType)
return true;
public Object readFrom(Class<Object> type, Type genericType,
Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, String> 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<String, Object> 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<JAXBContext> 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 as Your JAX-RS JSON Provider - Client Side MOXy as Your JAX-RS JSON Provider - Server Side Specifying EclipseLink MOXy as Your JAXB Provider更新
在 GlassFish 4 中,EclipseLink JAXB (MOXy) 是 Jersey 使用的默认 JSON 绑定提供程序:
http://blog.bdoughan.com/2013/06/moxy-is-new-default-json-binding.html【讨论】:
谢谢,我会试一试。我很想知道为什么这不能开箱即用——你知道吗?另外,我知道直到您发布答案:)
只是时间问题
@MДΓΓБДLL - 我已经更新了我的答案。我相信它不是开箱即用的,因为您收藏中的 then 项目没有 badge
作为根元素。
哦,有趣。再看看我可以解组的 JSON 和我不能解组的 JSON 之间的区别,我明白你的意思了。
这种方法不是标准的。要在 Jersey 中生成和使用标准 JSON,必须使用 Jackson!这很简单,快速,并且是由 Jersey 团队设计的。
@yvesamsellem - (-1?) 这种方法使用标准的“MessageBodyReader/Writer”接口与 Jersey 客户端 API 进行交互,并清楚地表明您不需要使用 Jackson 来生产和消费标准JSON 到泽西岛。早期版本的 MOXy 已在 GlassFish (blog.bdoughan.com/2012/02/glassfish-312-is-full-of-moxy.html) 中,包含 JSON 绑定的版本将包含在未来的 GlassFish 版本中。堆栈跟踪清楚地表明 JAXB 实现正在与 JSON 一起使用,并且 MOXy 支持比 Jackson 更多的 JAXB 注释。【参考方案3】:
默认情况下,Jersey 使用 JAXB 进行(非)编组过程,不幸的是,JAXB JSON 处理器不是标准的(忽略单元素数组,将空数组转换为单元素空数组。 ..)。
所以,你有两个选择:
-
将 JAXB 配置为更标准(see here 了解更多);
使用 Jackson 而不是 JAXB — 我建议这样做。
使用 Jackson 客户端的方式如下:
ClientConfig clientConfig = new DefaultClientConfig();
clientConfig.getFeatures().put(JSONConfiguration.FEATURE_POJO_MAPPING, Boolean.TRUE);
Client client = Client.create(clientConfig);
List<Badge> badges = client.resource("/badges").getEntity(new GenericType<List<Badge>>() );
【讨论】:
是的,正如my self-answer 所说,我正在尝试杰克逊,这似乎不那么古怪了。 很高兴知道。仅当您想要自定义(取消)编组过程时,才需要使用自定义 JacksonProvider。如果你不这样做,使用 Jersey 提供的默认值就足够了(JSONConfiguration 特性)。 碰巧的是,我实际上使用的是自定义提供程序,这样我就可以将 JAXB 注释与 Jackson 注释一起使用:wiki.fasterxml.com/… 默认的 Jersey Jackson 提供程序配置为使用 JAXB 注释和 Jackson 注释 ;-) 唯一的问题是该提供程序的确切配置没有记录:-(【参考方案4】:我遇到了类似的问题,通过以下方法解决了
像这样制作一个 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<JAXBContext>
private JAXBContext context;
private Class<?>[] types = Badge.class ;
private List<Class<?>> classes = new ArrayList<Class<?>>();
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;
将上下文解析器添加到您的客户端
ClientConfig config = new DefaultClientConfig();
config.getClasses().add(JAXBContextResolver.class);
Client client = Client.create(config);
现在你可以得到对象数组了
WebResource apiRoot = client.resource("http://localhost:9000/api");
Badge[] badges = apiRoot.path("/badges").get(Badge[].class);
如果你想要一个列表,只需使用
Arrays.asList(badges)
【讨论】:
【参考方案5】:导入这个
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-json</artifactId>
<version>1.17</version>
<scope>compile</scope>
</dependency>
这是解组的代码
import com.sun.jersey.api.json.JSONJAXBContext;
import com.sun.jersey.api.json.JSONUnmarshaller;
public static <T> T unmarshalJson(String jsonTxt, Class<T> clazz) throws JAXBException
JSONJAXBContext jctx = new JSONJAXBContext(clazz);
JSONUnmarshaller unm = jctx.createJSONUnmarshaller();
return (T)unm.unmarshalFromJSON(new StringReader(jsonTxt), clazz);
【讨论】:
【参考方案6】:这可能与生产者未将单例列表正确编码为 JSON 的问题有关。请参阅this article 以获得更完整的解释和建议的解决方案。
根据文章描述的内容以及错误消息,我猜测正在生成以下内容:
"id":"42",
"status":"Active",
"name":"purple monkey dishwasher"
根据文章,解决方案在于扩展和自定义提供程序,以纠正单例列表和空列表如何格式化为 JSON。
很遗憾,这篇文章是德文的,我必须自己翻译 - 如果它不能真正解决您的问题,请告诉我。如果确实如此,则归功于该文章的作者 Dirk Dittmar。
PS - 如果您像我一样使用 Chrome 翻译页面,请确保切换回原始代码以查看代码 sn-ps,因为它们的一部分被错误地“翻译”为空白。
【讨论】:
我(基本上)只使用JacksonJsonProvider
,而不是通过繁琐的创建自己的提供程序。
@MДΓΓБДLL - 听起来不错!感谢您的有趣帖子。以上是关于无法使用 Jersey 客户端解组 JSON 对象数组的主要内容,如果未能解决你的问题,请参考以下文章
json:无法将字符串解组为 MyMap.map 类型的 Go 值