Reputation: 359836
A one-element JSON array that I'm trying to unmarshal:
[
{
"id":"42",
"status":"Active",
"name":"purple monkey dishwasher"
}
]
The corresponding Java class (getters & setters omitted for brevity):
@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
}
}
The Jersey Client code which makes an HTTP request and is supposed to unmarshal the above JSON into a one-element 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>>(){});
The last line, specifically the WebResource#get()
call, throws the following exception:
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)
I've tried a variety of combinations of annotations on Badge
, or using an array instead of GenericType
:
List<Badge> badges = Arrays.asList(apiRoot.path("/badges").get(Badge[].class));
or using an intermediate ClientResponse
:
GenericType<List<Badge>> type = new GenericType<List<Badge>>(){};
ClientResponse clientResponse = apiRoot.path("/badges").get(ClientResponse.class);
List<Badge> badges = clientResponse.getEntity(type);
but none so far have solved the problem.
Even more confounding is the fact that my existing setup has no problems unmarshalling JSON-encoded Badge
s which are inside of other structures, like this:
{
"userid":"123456789",
"userbadges":[
{
"badge":{
"id":"42",
"status":"Active",
"name":"purple monkey dishwasher"
},
"earned":"2012-03-06 18:16:18.172"
}
]
}
What am I doing wrong?
Upvotes: 25
Views: 48286
Reputation: 149017
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
You can use the JSON Binding extension that is being added to the MOXy component in EclipseLink 2.4 to handle this use case:
Demo
The Jersey client API allows you to leverage the same MessageBodyReader
/MessageBodyWriter
from the server side on the client side.
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
Below is a generic MessageBodyReader
/MessageBodyWriter
that could be used with any server/client to enable MOXy as the JSON binding provider.
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;
}
}
}
For More Information
In GlassFish 4 EclipseLink JAXB (MOXy) is the default JSON-binding provider used by Jersey:
Upvotes: 14
Reputation:
Import this
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-json</artifactId>
<version>1.17</version>
<scope>compile</scope>
</dependency>
and this is the code to unmarshall
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);
}
Upvotes: 1
Reputation: 896
I had a similar problem, and was resolved with the following
Make a JAXB context resolver like this
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;
}
}
Added the context resolver to your client
ClientConfig config = new DefaultClientConfig();
config.getClasses().add(JAXBContextResolver.class);
Client client = Client.create(config);
Now you can get the objects array
WebResource apiRoot = client.resource("http://localhost:9000/api");
Badge[] badges = apiRoot.path("/badges").get(Badge[].class);
And if you want a list, simply use
Arrays.asList(badges)
Upvotes: 6
Reputation: 359836
I was able to solve this with minimal effort by using JacksonJsonProvider
as the MessageBody(Reader|Writer)
provider for the Jersey Client instance:
ClientConfig cfg = new DefaultClientConfig();
cfg.getClasses().add(JacksonJsonProvider.class);
Client client = Client.create(cfg);
Jackson's MessageBodyReader
implementation appears to be more well-behaved than the Jersey JSON one.
Thanks to How can I customize serialization of a list of JAXB objects to JSON? for pointing me in the Jackson direction.
Upvotes: 22
Reputation: 7244
By default, Jersey is using JAXB for the (un)marshalling process, and unfortunately, JAXB JSON processor is not standard (one-element arrays are ignored, empty arrays are transformed into a one-element empty array...).
So, you've got two choices:
Using Jackson client-side is done the following way:
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>>() {});
Upvotes: 11
Reputation: 55223
This may have to do with an issue in which the producer doesn't properly encode a singleton list into JSON. See this article for a fuller explanation and proposed solution.
Based on what the article describes, and from the error message, I'm guessing the following is being produced instead:
{
{
"id":"42",
"status":"Active",
"name":"purple monkey dishwasher"
}
}
According to the article, the solution lies in extending and customizing the provider to correct how singleton lists and empty lists are formatted into JSON.
Unfortunately the article is in German, which I had to translate for myself - let me know if it doesn't actually address your problem. If it does, credit goes to Dirk Dittmar, the article's author.
PS - if you use Chrome to translate the page like I did, make sure to switch back to the original to see the code snippets as parts of them get mistakenly "translated" into whitespace.
Upvotes: 0