Un tableau JSON à un élément que j'essaie de dissocier:
[
{
"id":"42",
"status":"Active",
"name":"purple monkey dishwasher"
}
]
La classe Java correspondante (getters & setters omis pour plus de brièveté):
@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
}
}
Le code client Jersey qui émet une requête HTTP est supposé pour décomparer le code JSON ci-dessus en un List<Foo>
à un élément:
Client client = Client.create();
WebResource apiRoot = client.resource("http://localhost:9000/api");
List<Badge> badges = apiRoot.path("/badges").get(new GenericType<List<Badge>>(){});
La dernière ligne, en particulier l'appel WebResource#get()
, lève l'exception suivante:
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)
J'ai essayé diverses combinaisons d'annotations sur Badge
ou en utilisant un tableau au lieu de GenericType
:
List<Badge> badges = Arrays.asList(apiRoot.path("/badges").get(Badge[].class));
ou en utilisant une variable ClientResponse
:
GenericType<List<Badge>> type = new GenericType<List<Badge>>(){};
ClientResponse clientResponse = apiRoot.path("/badges").get(ClientResponse.class);
List<Badge> badges = clientResponse.getEntity(type);
mais aucun n'a jusqu'ici résolu le problème.
Encore plus déconcertant est le fait que ma configuration existante a aucun problème unmarshalling Badge
s codés JSON qui sont à l'intérieur d'autres structures, comme ceci:
{
"userid":"123456789",
"userbadges":[
{
"badge":{
"id":"42",
"status":"Active",
"name":"purple monkey dishwasher"
},
"earned":"2012-03-06 18:16:18.172"
}
]
}
Qu'est-ce que je fais mal?
J'ai pu résoudre ce problème avec un effort minimal en utilisant JacksonJsonProvider
en tant que fournisseur MessageBody(Reader|Writer)
pour l'instance du client Jersey:
ClientConfig cfg = new DefaultClientConfig();
cfg.getClasses().add(JacksonJsonProvider.class);
Client client = Client.create(cfg);
L’application MessageBodyReader
de Jackson semble être plus sage que celle de Jersey JSON.
Merci à Comment personnaliser la sérialisation d’une liste d’objets JAXB en JSON? pour m'avoir orienté dans la direction de Jackson.
Remarque: Je suis le EclipseLink JAXB (MOXy) lead et membre du groupe JAXB (JSR-222) .
Vous pouvez utiliser l'extension de liaison JSON ajoutée au composant MOXy dans EclipseLink 2.4 pour gérer ce cas d'utilisation:
Démo
L’API client Jersey vous permet d’utiliser la même variable MessageBodyReader
MessageBodyWriter
côté serveur côté client.
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
Vous trouverez ci-dessous une variable MessageBodyReader
MessageBodyWriter
générique qui peut être utilisée avec n’importe quel serveur/client pour activer MOXy en tant que fournisseur de liaisons 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;
}
}
}
_/ Pour plus d'informations
Dans GlassFish 4, EclipseLink JAXB (MOXy) est le fournisseur de liaison JSON par défaut utilisé par Jersey:
Par défaut, Jersey utilise JAXB pour le processus de (dés) triage et, malheureusement, le processeur JAXB JSON n’est pas standard (les tableaux one-element sont ignorés, les tableaux vides sont transformés en tableau vide à un élément ... ).
Donc, vous avez deux choix:
L'utilisation de Jackson côté client se fait de la manière suivante:
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>>() {});
J'ai eu un problème similaire, et a été résolu avec ce qui suit
Faire un résolveur de contexte JAXB comme ceci
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;
}
}
Ajout du résolveur de contexte à votre client
ClientConfig config = new DefaultClientConfig();
config.getClasses().add(JAXBContextResolver.class);
Client client = Client.create(config);
Maintenant, vous pouvez obtenir le tableau d'objets
WebResource apiRoot = client.resource("http://localhost:9000/api");
Badge[] badges = apiRoot.path("/badges").get(Badge[].class);
Et si vous voulez une liste, utilisez simplement
Arrays.asList(badges)
Importer cette
<dependency>
<groupId>com.Sun.jersey</groupId>
<artifactId>jersey-json</artifactId>
<version>1.17</version>
<scope>compile</scope>
</dependency>
et ceci est le code pour 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);
}
Cela peut être dû à un problème dans lequel le producteur ne code pas correctement une liste de singleton dans JSON. Voir cet article pour une explication plus complète et la solution proposée.
D'après ce que décrit l'article et le message d'erreur, je suppose que les éléments suivants sont produits à la place:
{
{
"id":"42",
"status":"Active",
"name":"purple monkey dishwasher"
}
}
Selon l'article, la solution réside dans l'extension et la personnalisation du fournisseur afin de corriger le formatage des listes de singleton et des listes vides en JSON.
Malheureusement, l'article est en allemand, que j'ai dû traduire moi-même. Veuillez me faire savoir s'il ne résout pas votre problème. Dans ce cas, le mérite revient à Dirk Dittmar, l'auteur de l'article.
PS - Si vous utilisez Chrome pour traduire la page comme je l'ai fait, veillez à revenir à l'original pour afficher les extraits de code, car certaines parties d'entre eux sont par erreur "traduites" en espaces.