X
  • REST
    February 13, 2009

Configuring JSON for RESTful Web Services in Jersey 1.0.2

This is an update for a tech tip on configuring JSON in Jersey, which i wrote in October 2008. The way of JSON configuration, suggested in the tech tip, is now deprecated (but still functioning). Here i would like to describe the new API, which will hopefully last (and be supported) a way longer.

Notice: you will need to bundle jaxb-impl-2.1.10.jar
with your application in order to take advantage of the recently added JSON NATURAL convention

Deprecated Configuration

Configuring JSON format, as described in the tech tip, meant to implement a JAXBContext resolver
class returning an instance of JSONJAXBContext. This principle have not changed.
What changed is a way, how the JSONJAXBContext itself is being configured.
Lets look at the sample code below (using the deprecated API):


   @Provider
public class MyJAXBContextResolver implements ContextResolver<JAXBContext> {
private JAXBContext context;
private Class[] types = {StatusInfoBean.class, JobInfoBean.class};
public MyJAXBContextResolver() throws Exception {
Map props = new HashMap<String, Object>();
props.put(JSONJAXBContext.JSON_NOTATION, JSONJAXBContext.JSONNotation.MAPPED);
props.put(JSONJAXBContext.JSON_ROOT_UNWRAPPING, Boolean.TRUE);
props.put(JSONJAXBContext.JSON_ARRAYS, new HashSet<String>(1){{add("jobs");}});
props.put(JSONJAXBContext.JSON_NON_STRINGS, new HashSet<String>(1){{add("pages"); add("tonerRemaining");}});
this.context = new JSONJAXBContext(types, props);
}
public JAXBContext getContext(Class<?> objectType) {
return (types[0].equals(objectType)) ? context : null;
}
}




There you needed to create a property bag, put appropriate configuration options into it,
and then pass it to the JSONJAXBContext constructor.

Jersey 1.0.2 JSON Configuration

In the currently available 1.0.2 Jersey version, a new JSONConfiguration class was introduced to became a central point for JSON configuration options.
For creating a new JSONConfiguration instance, a builder pattern is employed.
It is not only more user friendly, but also ensures only meaningful JSON options could be combined together.
You can compare the following code, with the deprecated example above:


   @Provider
public class MyJAXBContextResolver implements ContextResolver<JAXBContext> {
private JAXBContext context;
private Class[] types = {StatusInfoBean.class, JobInfoBean.class};
public MyJAXBContextResolver() throws Exception {
this.context = new JSONJAXBContext(
JSONConfiguration.mapped()
.rootUnwrapping(true)
.arrays("jobs")
.nonStrings("pages", "tonerRemaining")
.build(),
types);
}
public JAXBContext getContext(Class<?> objectType) {
return (types[0].equals(objectType)) ? context : null;
}
}




You can look at JSONConfiguration javadoc for detailed information on various configuration options.

Further Simplification

If you go a bit further, you can ask if the configuration could be simplified even more.
Imagine you have much bigger number of JAXB beans in your model, and they are more complex.
It could easily become unmanageable to maintain a reasonable JSON configuration as described so far.
Then if you happen to have conflicting non-string/string values and/or arrays/non-arrays elements in your set,
you could easily run out of options there.

A natural way to overcome above mentioned issues, is to simply use recently introduced Jersey NATURAL JSON notation. Then you need only to:


   @Provider
public class MyJAXBContextResolver implements ContextResolver<JAXBContext> {
private JAXBContext context;
private Class[] types = {StatusInfoBean.class, JobInfoBean.class};
public MyJAXBContextResolver() throws Exception {
this.context = new JSONJAXBContext(
JSONConfiguration.natural().build(),
types);
}
public JAXBContext getContext(Class<?> objectType) {
return (types[0].equals(objectType)) ? context : null;
}
}




Such configuration is simple from user point of view, but yet very powerful. You do not need to keep
various configuration options in sync with your actual JAXB beans, and be worried what options to actually use
(what exact names, etc.). Jersey will automatically take care about
serializing Java collections/arrays as JSON arrays, Java booleans as JSON booleans, Java ints as JSON integers,and so on.

Join the discussion

Comments ( 11 )
  • mohammadwrk Sunday, February 15, 2009

    Thanks for the update. I just updated my JAXBContextResolver to use the new NATURAL JSON notation and got the following exception! Since I'm using JDK1.6.0_11 and glassfish-v2ur2, wondering on what environment you guys have tested this new feature?

    javax.xml.bind.JAXBException: property "retainReferenceToInfo" is not supported

    at com.sun.xml.bind.v2.ContextFactory.createContext(ContextFactory.java:114)

    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)

    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)

    at java.lang.reflect.Method.invoke(Method.java:597)

    at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:211)

    at javax.xml.bind.ContextFinder.find(ContextFinder.java:372)

    at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:574)

    at com.sun.jersey.api.json.JSONJAXBContext.<init>(JSONJAXBContext.java:256)


  • Jakub Monday, February 16, 2009

    @mohammadwrk: you need to explicitly use the latest jaxb-impl-2.1.10.jar with your project, as the new feature is not supported for the previous jaxb ri releases. And the latest one has not been integrated yet to the jdk1.6.


  • guest Monday, February 16, 2009

    Very nice,

    but why do I acutally have to register my Classes. Couldn't it be possible to have natural as the default and register exceptions?


  • Mark Rabick Tuesday, February 17, 2009

    I'm having a similar problem ---

    retainReferenceToInfo is not supported. I downloaded the http://download.java.net/maven/1/com.sun.xml.bind/jars/jaxb-impl-2.1.10.jar and still get the exception when my app deploys. Using Weblogic 10.0, Java 1.5.0_17 and Jersey 1.0.2 distribution files from http://download.java.net/maven/2/com/sun/jersey/jersey-archive/1.0.2/jersey-archive-1.0.2.zip.

    I went through the dependencies document and retrieved the remaining jars listed for JAXB. Please advise. The stacktrace is here:

    Feb 17, 2009 3:07:11 PM com.sun.jersey.api.core.PackagesResourceConfig init

    INFO: Provider classes found:

    class mil.cnodb.rs.config.EJBProvider

    class mil.cnodb.rs.config.JAXBContextResolver

    Feb 17, 2009 3:07:12 PM com.sun.jersey.core.spi.component.ProviderFactory _getComponentProvider

    SEVERE: The provider class, class mil.cnodb.rs.config.JAXBContextResolver, could not be instantiated

    javax.xml.bind.JAXBException: property "retainReferenceToInfo" is not supported

    at com.sun.xml.bind.v2.ContextFactory.createContext(ContextFactory.java:52)

    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)

    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)

    at java.lang.reflect.Method.invoke(Method.java:585)

    at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:210)

    at javax.xml.bind.ContextFinder.find(ContextFinder.java:368)

    at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:574)

    at com.sun.jersey.api.json.JSONJAXBContext.<init>(JSONJAXBContext.java:256)

    at mil.cnodb.rs.config.JAXBContextResolver.<init>(JAXBContextResolver.java:39)

    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)

    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)

    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)

    at java.lang.reflect.Constructor.newInstance(Constructor.java:494)

    at java.lang.Class.newInstance0(Class.java:350)

    at java.lang.Class.newInstance(Class.java:303)

    at com.sun.jersey.core.spi.component.ComponentConstructor._getInstance(ComponentConstructor.java:154)

    at com.sun.jersey.core.spi.component.ComponentConstructor.getInstance(ComponentConstructor.java:143)

    at com.sun.jersey.core.spi.component.ProviderFactory.getInstance(ProviderFactory.java:206)

    at com.sun.jersey.core.spi.component.ProviderFactory._getComponentProvider(ProviderFactory.java:133)

    at com.sun.jersey.core.spi.component.ProviderFactory.getComponentProvider(ProviderFactory.java:126)

    at com.sun.jersey.core.spi.component.ProviderServices.getComponent(ProviderServices.java:168)

    at com.sun.jersey.core.spi.component.ProviderServices.getProviders(ProviderServices.java:95)

    at com.sun.jersey.core.spi.factory.ContextResolverFactory.<init>(ContextResolverFactory.java:90)

    at com.sun.jersey.server.impl.application.WebApplicationImpl.initiate(WebApplicationImpl.java:410)

    at com.sun.jersey.server.impl.application.WebApplicationImpl.initiate(WebApplicationImpl.java:317)

    at com.sun.jersey.spi.container.servlet.WebComponent.initiate(WebComponent.java:422)

    at com.sun.jersey.spi.container.servlet.WebComponent.load(WebComponent.java:433)

    at com.sun.jersey.spi.container.servlet.WebComponent.init(WebComponent.java:167)

    at com.sun.jersey.spi.container.servlet.ServletContainer.init(ServletContainer.java:197)

    at weblogic.servlet.internal.StubSecurityHelper$ServletInitAction.run(StubSecurityHelper.java:282)

    at weblogic.security.acl.internal.AuthenticatedSubject.doAs(AuthenticatedSubject.java:321)

    at weblogic.security.service.SecurityManager.runAs(Unknown Source)

    at weblogic.servlet.internal.StubSecurityHelper.createServlet(StubSecurityHelper.java:63)

    at weblogic.servlet.internal.StubLifecycleHelper.createOneInstance(StubLifecycleHelper.java:58)

    at weblogic.servlet.internal.StubLifecycleHelper.<init>(StubLifecycleHelper.java:48)

    at weblogic.servlet.internal.ServletStubImpl.prepareServlet(ServletStubImpl.java:507)

    at weblogic.servlet.internal.WebAppServletContext.preloadServlet(WebAppServletContext.java:1853)

    at weblogic.servlet.internal.WebAppServletContext.loadServletsOnStartup(WebAppServletContext.java:1830)

    at weblogic.servlet.internal.WebAppServletContext.preloadResources(WebAppServletContext.java:1750)

    at weblogic.servlet.internal.WebAppServletContext.start(WebAppServletContext.java:2909)

    at weblogic.servlet.internal.WebAppModule.startContexts(WebAppModule.java:973)

    at weblogic.servlet.internal.WebAppModule.start(WebAppModule.java:361)

    at weblogic.application.internal.flow.ModuleStateDriver$3.next(ModuleStateDriver.java:204)

    at weblogic.application.utils.StateMachineDriver.nextState(StateMachineDriver.java:26)

    at weblogic.application.internal.flow.ModuleStateDriver.start(ModuleStateDriver.java:60)

    at weblogic.application.internal.flow.ScopedModuleDriver.start(ScopedModuleDriver.java:200)

    at weblogic.application.internal.flow.ModuleListenerInvoker.start(ModuleListenerInvoker.java:117)

    at weblogic.application.internal.flow.ModuleStateDriver$3.next(ModuleStateDriver.java:204)

    at weblogic.application.utils.StateMachineDriver.nextState(StateMachineDriver.java:26)

    at weblogic.application.internal.flow.ModuleStateDriver.start(ModuleStateDriver.java:60)

    at weblogic.application.internal.flow.StartModulesFlow.activate(StartModulesFlow.java:26)

    at weblogic.application.internal.BaseDeployment$2.next(BaseDeployment.java:635)

    at weblogic.application.utils.StateMachineDriver.nextState(StateMachineDriver.java:26)

    at weblogic.application.internal.BaseDeployment.activate(BaseDeployment.java:212)

    at weblogic.application.internal.DeploymentStateChecker.activate(DeploymentStateChecker.java:154)

    at weblogic.deploy.internal.targetserver.AppContainerInvoker.activate(AppContainerInvoker.java:80)

    at weblogic.deploy.internal.targetserver.operations.AbstractOperation.activate(AbstractOperation.java:566)

    at weblogic.deploy.internal.targetserver.operations.ActivateOperation.activateDeployment(ActivateOperation.java:136)

    at weblogic.deploy.internal.targetserver.operations.ActivateOperation.doCommit(ActivateOperation.java:104)

    at weblogic.deploy.internal.targetserver.operations.StartOperation.doCommit(StartOperation.java:139)

    at weblogic.deploy.internal.targetserver.operations.AbstractOperation.commit(AbstractOperation.java:320)

    at weblogic.deploy.internal.targetserver.DeploymentManager.handleDeploymentCommit(DeploymentManager.java:816)

    at weblogic.deploy.internal.targetserver.DeploymentManager.activateDeploymentList(DeploymentManager.java:1223)

    at weblogic.deploy.internal.targetserver.DeploymentManager.handleCommit(DeploymentManager.java:434)

    at weblogic.deploy.internal.targetserver.DeploymentServiceDispatcher.commit(DeploymentServiceDispatcher.java:161)

    at weblogic.deploy.service.internal.targetserver.DeploymentReceiverCallbackDeliverer.doCommitCallback(DeploymentReceiverCallbackDeliverer.java

    :181)

    at weblogic.deploy.service.internal.targetserver.DeploymentReceiverCallbackDeliverer.access$100(DeploymentReceiverCallbackDeliverer.java:12)

    at weblogic.deploy.service.internal.targetserver.DeploymentReceiverCallbackDeliverer$2.run(DeploymentReceiverCallbackDeliverer.java:67)

    at weblogic.work.SelfTuningWorkManagerImpl$WorkAdapterImpl.run(SelfTuningWorkManagerImpl.java:464)

    at weblogic.work.ExecuteThread.execute(ExecuteThread.java:200)

    at weblogic.work.ExecuteThread.run(ExecuteThread.java:172)


  • mohammadwrk Tuesday, February 17, 2009

    @Mark

    Switching to jaxb-impl-2.1.10 worked for me. I think in your case Weblogic still loading ContextFactory from the old jar file. To trace where it loads from add "-verbose" to java command that launches Weblogic and then search for ContextFactory.


  • Mark Rabick Wednesday, February 18, 2009

    -mohammadwrk

    it looks like it is loading it from the embedded Glassfish jars shipped with weblogic...

    [Loaded com.sun.xml.bind.v2.ContextFactory from file:/C:/bea/modules/glassfish.jaxb_1.0.1.0_2-0-5.jar]

    do you know how to get the app to 'prefer' the jars contained in the WEB-INF/lib directory of the war?


  • Mark Rabick Wednesday, February 18, 2009

    @mohammadwrd

    Figured it out. Added element to weblogic.xml

    <container-descriptor>

    <!--

    This setting will force the classloader to load the

    com.sun.xml.bind.v2.ContextFactory classes

    from the WEB-INF/lib jars (jaxb-impl-2.1.10.jar)

    -->

    <prefer-web-inf-classes>true</prefer-web-inf-classes>

    </container-descriptor>


  • Mohammadwrk Wednesday, February 18, 2009

    @193.171.188.3

    Agree, we shouldn't be forced to register classes and I think Jersey should provide a provider out-of-the-box to so.

    However, for now you can use the following modified version of Jakub's implementation to archive the same goal easily.

    /\*\*

    \*

    \* @author Mohammad

    \*/

    @Provider @Produces(MediaType.APPLICATION_JSON)

    public class JAXBContextResolver implements ContextResolver<JAXBContext> {

    private JAXBContext context;

    public JAXBContextResolver() throws Exception {

    AnnotatedClassScanner classScanner = new AnnotatedClassScanner(

    XmlRootElement.class, XmlType.class);

    Set<Class<?>> classes = classScanner.scan(new String[]{"com.yourcompany.yourproject"});

    this.context = new JSONJAXBContext(JSONConfiguration.natural().build(),

    classes.toArray(new Class[classes.size()]));

    }

    public JAXBContext getContext(Class<?> objectType) {

    return context;

    }

    }


  • Jakub Wednesday, February 18, 2009

    @193.171.188.3, Mohammad: The reason for not making the NATURAL notation the default one, was we simply did not want to break backward compatibility for existing users. The time for it's adoption was very short. I want to make NATURAL the default notation in one of the upcoming releases. Besides the question, what should be the default, i would also like to introduce a web application feature allowing to configure JSON notation globally for a whole application, whithout a need to specify a JAXB resolver.


  • Tony Nys Wednesday, April 22, 2009

    Apparently it even works in jersey 1.0.3 without this myjaxbcontentresolver class (jsconfiguration.natural.build())

    Everything is out of the box ok.. ?

    Wired stuff with spring, really cool, using json..


  • xmariachi Monday, July 6, 2009

    Hi,

    I think that it would be great to have a simple way to set the default configuration to some value. Certain applications may want to change the default, but still have one and then apply the particular classes changes by specifying the classes like you do on the example if needed.

    Cheers


Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.Captcha