Search code examples
javajsonjakarta-eejerseypayara

JSON serialization skipping child elements


I am running a payara micro server and having some difficulties serializing an object.

The root object has a child object which is either of type A or B extends A. The issue is that it always serialized into json as if it was of type A, i.e. no properties that are in the type B are being serialized.

I have checked that the object is correct right after returning the Response object.

The pom.xml looks like this:

<build>
    <finalName>profile</finalName>

    <plugins>
        <plugin>
            <groupId>fish.payara.maven.plugins</groupId>
            <artifactId>payara-micro-maven-plugin</artifactId>
            <version>1.0.0</version>
            <executions>
                <execution>
                    <goals>
                        <goal>bundle</goal>
                        <goal>start</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <payaraVersion>4.1.2.173</payaraVersion>
                <useUberJar>true</useUberJar>
                <deployWar>false</deployWar>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
    </plugins>
</build>

<dependencies>
    <dependency>
        <groupId>org.eclipse.microprofile</groupId>
        <artifactId>microprofile-bom</artifactId>
        <version>1.1.0</version>
        <type>pom</type>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>javax</groupId>
        <artifactId>javaee-api</artifactId>
        <version>7.0</version>
    </dependency>
    <dependency>
        <groupId>com.sun.jersey</groupId>
        <artifactId>jersey-json</artifactId>
        <version>1.19.4</version>
    </dependency>
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>LATEST</version>
    </dependency>
    <dependency>
        <groupId>io.swagger</groupId>
        <artifactId>swagger-jaxrs</artifactId>
        <version>1.5.20</version>
    </dependency>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-core</artifactId>
        <version>5.3.1.Final</version>
    </dependency>
    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
        <version>9.4.1212</version>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.9.0</version>
    </dependency>
</dependencies>

And I was thinking that maybe I should just change to org.glassfish.jersey.media.jersey-media-json-jackson and maybe that would help, but then I just get this exception HK2 failure has been detected in a code that does not run in an active Jersey Error scope.

So I am not really sure what to do at this point. Is there any way to config the com.sun.jersey.jersey-json parser, or should I just give up on that and try to force jersey-media-json-jackson in there instead?

edit: I've realized that payara uses MOXy json by default, but the issue is still the same

To show some examples, I have this somewhat shortened code:

public class CategoryDTO {
    private String name;
    private List<QuestionDTO> questions;
}

public class QuestionDTO {
    private String id;
}

public class QuestionAdminDTO extends QuestionDTO {
    private String comment;
}

and when it is serialized I get

{
   "name":"some name",
   "questions":[
       {
          "id":"some id"
       }
   ]
}

but I am expecting that comment should be included like this

{
   "name":"some name",
   "questions":[
       {
          "id":"some id",
          "comment":"some comment"
       }
   ]
}

After testing some more it works if I try to marshal a QuestionDTO object straight away, but not if it is nestled

My application is configured with a /webapp/WEB-INF/glassfish-web.xml file that looks like this

<!DOCTYPE glassfish-web-app PUBLIC "-//GlassFish.org//DTD
GlassFish Application Server 3.1 Servlet 3.0//EN"
        "http://glassfish.org/dtds/glassfish-web-app_3_0-1.dtd">
<glassfish-web-app>
    <session-config>
        <session-manager/>
    </session-config>
    <class-loader delegate="false"/>
</glassfish-web-app>

As well as a "custom" Application class:

@ApplicationPath("/api")
public class RestApplication extends Application {

    public RestApplication() {
        Swagger swagger = new Swagger();
        swagger.securityDefinition("JWT_AUTHORIZATION", new ApiKeyAuthDefinition("access_token", In.HEADER));
        new SwaggerContextService().updateSwagger(swagger);

        BeanConfig beanConfig = new BeanConfig();
        beanConfig.setDescription("profile");
        beanConfig.setTitle("Profile");
        beanConfig.setVersion("1.0-SNAPSHOT");
        beanConfig.setSchemes(new String[]{"http"});
        beanConfig.setBasePath("/profile/api");
        beanConfig.setResourcePackage("se.profile");
        beanConfig.setScan(true);
    }
}

the stacktrace I get when adding jackson

[2018-06-21T10:37:26.526+0200] [] [WARNING] [] [org.glassfish.jersey.internal.Errors] [tid: _ThreadID=1 _ThreadName=main] [timeMillis: 1529570246526] [levelValue: 900] [[
  The following warnings have been detected: WARNING: HK2 failure has been detected in a code that does not run in an active Jersey Error scope.
WARNING: Unknown HK2 failure detected:
MultiException stack 1 of 1
javax.enterprise.inject.CreationException
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at java.lang.Class.newInstance(Class.java:442)
    at org.jboss.weld.security.NewInstanceAction.run(NewInstanceAction.java:33)
    at java.security.AccessController.doPrivileged(Native Method)
    at org.jboss.weld.injection.Exceptions.rethrowException(Exceptions.java:40)
    at org.jboss.weld.injection.Exceptions.rethrowException(Exceptions.java:50)
    at org.jboss.weld.injection.Exceptions.rethrowException(Exceptions.java:90)
    at org.jboss.weld.injection.ConstructorInjectionPoint.newInstance(ConstructorInjectionPoint.java:127)
    at org.jboss.weld.injection.ConstructorInjectionPoint.invokeAroundConstructCallbacks(ConstructorInjectionPoint.java:92)
    at org.jboss.weld.injection.ConstructorInjectionPoint.newInstance(ConstructorInjectionPoint.java:78)
    at org.jboss.weld.injection.producer.AbstractInstantiator.newInstance(AbstractInstantiator.java:28)
    at org.jboss.weld.injection.producer.BasicInjectionTarget.produce(BasicInjectionTarget.java:112)
    at org.jboss.weld.injection.producer.BeanInjectionTarget.produce(BeanInjectionTarget.java:180)
    at org.glassfish.jersey.ext.cdi1x.internal.AbstractCdiBeanHk2Factory$2.getInstance(AbstractCdiBeanHk2Factory.java:139)
    at org.glassfish.jersey.ext.cdi1x.internal.AbstractCdiBeanHk2Factory._provide(AbstractCdiBeanHk2Factory.java:91)
    at org.glassfish.jersey.ext.cdi1x.internal.GenericCdiBeanHk2Factory.provide(GenericCdiBeanHk2Factory.java:63)
    at org.jvnet.hk2.internal.FactoryCreator.create(FactoryCreator.java:153)
    at org.jvnet.hk2.internal.SystemDescriptor.create(SystemDescriptor.java:487)
    at org.jvnet.hk2.internal.PerLookupContext.findOrCreate(PerLookupContext.java:70)
    at org.jvnet.hk2.internal.Utilities.createService(Utilities.java:2126)
    at org.jvnet.hk2.internal.ServiceLocatorImpl.internalGetService(ServiceLocatorImpl.java:777)
    at org.jvnet.hk2.internal.ServiceLocatorImpl.internalGetService(ServiceLocatorImpl.java:740)
    at org.jvnet.hk2.internal.ServiceLocatorImpl.getService(ServiceLocatorImpl.java:710)
    at org.glassfish.jersey.server.ApplicationHandler.createApplication(ApplicationHandler.java:385)
    at org.glassfish.jersey.server.ApplicationHandler.<init>(ApplicationHandler.java:342)
    at org.glassfish.jersey.servlet.WebComponent.<init>(WebComponent.java:392)
    at org.glassfish.jersey.servlet.ServletContainer.init(ServletContainer.java:177)
    at org.glassfish.jersey.servlet.ServletContainer.init(ServletContainer.java:369)
    at javax.servlet.GenericServlet.init(GenericServlet.java:244)
    at org.apache.catalina.core.StandardWrapper.initServlet(StandardWrapper.java:1495)
    at org.apache.catalina.core.StandardWrapper.load(StandardWrapper.java:1294)
    at org.apache.catalina.core.StandardContext.loadOnStartup(StandardContext.java:5303)
    at org.apache.catalina.core.StandardContext.start(StandardContext.java:5548)
    at com.sun.enterprise.web.WebModule.start(WebModule.java:522)
    at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:917)
    at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:900)
    at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:684)
    at com.sun.enterprise.web.WebContainer.loadWebModule(WebContainer.java:2057)
    at com.sun.enterprise.web.WebContainer.loadWebModule(WebContainer.java:1703)
    at com.sun.enterprise.web.WebApplication.start(WebApplication.java:107)
    at org.glassfish.internal.data.EngineRef.start(EngineRef.java:122)
    at org.glassfish.internal.data.ModuleInfo.start(ModuleInfo.java:294)
    at org.glassfish.internal.data.ApplicationInfo.start(ApplicationInfo.java:357)
    at com.sun.enterprise.v3.server.ApplicationLifecycle.deploy(ApplicationLifecycle.java:501)
    at com.sun.enterprise.v3.server.ApplicationLifecycle.deploy(ApplicationLifecycle.java:220)
    at org.glassfish.deployment.admin.DeployCommand.execute(DeployCommand.java:488)
    at com.sun.enterprise.v3.admin.CommandRunnerImpl$2$1.run(CommandRunnerImpl.java:544)
    at com.sun.enterprise.v3.admin.CommandRunnerImpl$2$1.run(CommandRunnerImpl.java:540)
    at java.security.AccessController.doPrivileged(Native Method)
    at javax.security.auth.Subject.doAs(Subject.java:360)
    at com.sun.enterprise.v3.admin.CommandRunnerImpl$2.execute(CommandRunnerImpl.java:539)
    at com.sun.enterprise.v3.admin.CommandRunnerImpl$3.run(CommandRunnerImpl.java:570)
    at com.sun.enterprise.v3.admin.CommandRunnerImpl$3.run(CommandRunnerImpl.java:562)
    at java.security.AccessController.doPrivileged(Native Method)
    at javax.security.auth.Subject.doAs(Subject.java:360)
    at com.sun.enterprise.v3.admin.CommandRunnerImpl.doCommand(CommandRunnerImpl.java:561)
    at com.sun.enterprise.v3.admin.CommandRunnerImpl.doCommand(CommandRunnerImpl.java:1469)
    at com.sun.enterprise.v3.admin.CommandRunnerImpl.access$1300(CommandRunnerImpl.java:111)
    at com.sun.enterprise.v3.admin.CommandRunnerImpl$ExecutionContext.execute(CommandRunnerImpl.java:1851)
    at com.sun.enterprise.v3.admin.CommandRunnerImpl$ExecutionContext.execute(CommandRunnerImpl.java:1727)
    at com.sun.enterprise.admin.cli.embeddable.DeployerImpl.deploy(DeployerImpl.java:134)
    at com.sun.enterprise.admin.cli.embeddable.DeployerImpl.deploy(DeployerImpl.java:149)
    at fish.payara.micro.impl.PayaraMicroImpl.deployAll(PayaraMicroImpl.java:1377)
    at fish.payara.micro.impl.PayaraMicroImpl.bootStrap(PayaraMicroImpl.java:993)
    at fish.payara.micro.impl.PayaraMicroImpl.main(PayaraMicroImpl.java:186)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at fish.payara.micro.boot.loader.MainMethodRunner.run(MainMethodRunner.java:48)
    at fish.payara.micro.boot.loader.Launcher.launch(Launcher.java:107)
    at fish.payara.micro.boot.loader.Launcher.launch(Launcher.java:70)
    at fish.payara.micro.boot.PayaraMicroLauncher.main(PayaraMicroLauncher.java:79)
    at fish.payara.micro.PayaraMicro.main(PayaraMicro.java:361)
Caused by: java.lang.NoClassDefFoundError: com/fasterxml/jackson/annotation/JsonMerge
    at com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector.<clinit>(JacksonAnnotationIntrospector.java:50)
    at com.fasterxml.jackson.databind.ObjectMapper.<clinit>(ObjectMapper.java:291)
    at io.swagger.util.ObjectMapperFactory.create(ObjectMapperFactory.java:35)
    at io.swagger.util.ObjectMapperFactory.createJson(ObjectMapperFactory.java:23)
    at io.swagger.util.ObjectMapperFactory.createJson(ObjectMapperFactory.java:19)
    at io.swagger.util.Json.mapper(Json.java:13)
    at io.swagger.jaxrs.DefaultParameterExtension.<init>(DefaultParameterExtension.java:50)
    at io.swagger.jaxrs.ext.SwaggerExtensions.<clinit>(SwaggerExtensions.java:36)
    at io.swagger.jaxrs.utils.ReaderUtils.collectParameters(ReaderUtils.java:123)
    at io.swagger.jaxrs.utils.ReaderUtils.collectFieldParameters(ReaderUtils.java:96)
    at io.swagger.jaxrs.Reader.read(Reader.java:291)
    at io.swagger.jaxrs.Reader.read(Reader.java:175)
    at io.swagger.jaxrs.config.BeanConfig.scanAndRead(BeanConfig.java:242)
    at io.swagger.jaxrs.config.BeanConfig.setScan(BeanConfig.java:221)
    at se.profile.RestApplication.<init>(RestApplication.java:30)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at org.jboss.weld.injection.ConstructorInjectionPoint.newInstance(ConstructorInjectionPoint.java:119)
    ... 66 more
Caused by: java.lang.ClassNotFoundException: com.fasterxml.jackson.annotation.JsonMerge
    at org.glassfish.web.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1655)
    at org.glassfish.web.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1501)
    ... 86 more

]]

Solution

  • Easiest solution is to use Jackson. MOXy is based off JAXB rules, which can get very complicated in some situations (this includes working with inheritance). Jackson just works.

    Just add the Jackson dependency (in a provided scope, as Payara already has the dependency) and register the JacksonFeature with the application.

    <dependency>
        <groupId>org.glassfish.jersey.media</groupId>
        <artifactId>jersey-media-json-jackson</artifactId>
        <version>2.25.1</version>
        <scope>provided</scope>
    </dependency>
    

    I can't see how you configuring your application, so I can't advise on the appropriate way to register the JacksonFeature. If you don't know how to do this, you can either update your post with your application configuration or you can just search for how you register it.

    Update

    To register the JacksonFeature in the Application class, what you can do is use a property.

    public class RestAppication extends Application {
    
        @Override
        public Map<String, Object> getProperties() {
            Map<String, Object> props = new HashSet<>();
    
            String classnames = JacksonFeature.class.getName();
            props.put("jersey.config.server.provider.classnames", classnames);
            props.put("jersey.config.disableMoxyJson", true);
            return props;
        }
    }
    

    If you get a Jackson dependency conflict, which might occur with your use of Swagger (which pulls Jackson in), you cam just exclude Jackson from Swagger, as the server should already have the dependencies

    <dependency>
        <groupId>io.swagger</groupId>
        <artifactId>swagger-jaxrs</artifactId>
        <version>1.5.20</version>
        <exclusions>
            <exclusion>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-databind</artifactId>
            </exclusion>
            <exclusion>
                <groupId>com.fasterxml.jackson.module</groupId>
                <artifactId>jackson-module-jaxb-annotations</artifactId>
            <exclusion>
        </exclusions>
    </dependency>