Including null elements in JSON output of Jersey RESTful API with JAXB
For EclipseLink JAXB (MOXy)'s JSON binding, the correct mapping would be the following. You could try it with your provider to see if it would work also:
@XmlRootElement
public class Data {
@XmlElement(nillable=true)
public String firstName;
@XmlElement(nillable=true)
public String lastName;
}
For More Information
- http://blog.bdoughan.com/2012/04/binding-to-json-xml-handling-null.html
- http://blog.bdoughan.com/2012/05/moxy-as-your-jax-rs-json-provider.html
UPDATE 2
EclipseLink 2.4 includes MOXyJsonProvider
which is an implementation of MessageBodyReader
/MessageBodyWriter
that you can use directly to leverage MOXy's JSON binding
UPDATE 1
The following MessageBodyReader
/MessageBodyWriter
may work better for you:
import java.io.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import javax.xml.transform.stream.StreamSource;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import javax.ws.rs.ext.*;
import javax.xml.bind.*;
import org.eclipse.persistence.jaxb.JAXBContextFactory;
@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 {
Class<?> domainClass = getDomainClass(genericType);
Marshaller m = getJAXBContext(domainClass, 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 JAXBContextFactory.createContext(new Class[] {type}, null);
} 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;
}
}
}
Comments
-
Oleksi almost 2 years
I have a class that I would like to expose through a Jersey RESTful API. It looks similar to this:
@XmlRootElement public class Data { public String firstName; public String lastName; }
My problem is that these fields may be null, in which case the field is omitted from the JSON output. I would like all fields to be present regardless of their value. For example, if lastName is null, the JSON output will be:
{ "firstName" : "Oleksi" }
instead of what I want:
{ "firstName" : "Oleksi", "lastName" : null }
I have a JAXBContextResolver (an implementation of ContextResolver) that looks like this:
@Provider public class JAXBContextResolver implements ContextResolver<JAXBContext> { // internal state private final JAXBContext context; private final Set<Class> types; private final Class[] cTypes = { Data.class }; public JAXBContextResolver() throws Exception { types = new HashSet(Arrays.asList(cTypes)); context = new JSONJAXBContext(JSONConfiguration.natural().humanReadableFormatting(true).build(), cTypes); } @Override public JAXBContext getContext(Class<?> objectType) { return (types.contains(objectType)) ? context : null; } }
I've been trying to figure out how to get that desired output for a while, but I've had no luck. I'm open to trying other ContextResolvers/Serializers, but I haven't been able to find one that works, so code examples would be nice.
-
Oleksi about 12 yearsI've tried this, but I can't quite get it to work. Can you please provide more details on any additional code changes that would be required (other than what's listed above). For instance, do I have to change my JAXBContextResolver class?
-
Oleksi about 12 yearsI've also had difficulties downloading the required dependancies for this project. I tried looking at this document to no avail. wiki.eclipse.org/EclipseLink/Maven
-
bdoughan about 12 yearsHere is an example pom.xml: github.com/bdoughan/blog20110819/blob/master/pom.xml
-
Oleksi about 12 yearsI downloaded and tried it with the JAXBProvider and it didn't work. An empty lastName is output lastName : { "nil" : "true" }. I switched to MOXyJSONProvider given in your example and then I get an exception: javax.ws.rs.WebApplicationException: javax.xml.bind.PropertyException: name: eclipselink.media-type value: application/json at MOXyJSONProvider.writeTo(MOXyJSONProvider.java:58)
-
Oleksi about 12 yearsDoes it have to be version 2.4.0-SNAPSHOT? I'd prefer to use the most up-to-date stable release (2.3.2)
-
bdoughan about 12 years@Oleksi - The JSON binding is being added to 2.4.0, instead of a snap shot you can use a milestone 2.4.0-M18 eclipse.org/eclipselink/downloads/milestones.php. We will be releasing as part of the Eclipse Juno release this summer and should be entering the release candidate stage very soon.
-
Oleksi about 12 yearsI downloaded and tried it with 2.4.0-M18, but I'm still getting the same exception. Any suggestions?
-
Oleksi about 12 yearsLooks like getJAXBContext() in MOXyJSONProvider might not be picking up a context correctly (and thus creating a JAXB context that doesn't know what "eclipselink.media-type" is). Do we also need to provide a JAXBContext? If so, how do we tell MOXy about it?
-
bdoughan about 12 years@Oleksi - I've updated my answer with a version that may work better for you.
-
Oleksi about 12 yearsTried the new version, but it still doesn't work (same exception). I think there's some type confusion in getJAXBContext(). Should it be working with avax.xml.bind.JAXBContext or org.eclipse.persistence.jaxb.JAXBContext? Or both? Also, do I need the jaxb.properties file, and if so, then where should it be?
-
Oleksi about 12 yearsFixed! The problem was the we left the old JAXB provider hanging around with it's @Provider annotation. It seems to work now. Thanks a a lot for your help. :)