Cannot unmarshal a JSON array of objects using Jersey Client Cannot unmarshal a JSON array of objects using Jersey Client json json

Cannot unmarshal a JSON array of objects using Jersey Client


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.


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


UPDATE

In GlassFish 4 EclipseLink JAXB (MOXy) is the default JSON-binding provider used by Jersey:


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:

  1. configuring JAXB to be more standard (see here for more);
  2. using Jackson instead of JAXB — which I recommend.

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>>() {});