javajsonjson-deserializationjsonb-api

How to deserialize a JSON string to a non-public class using JSON-B?


I've created a trival Java 9 Maven app with two classes to test the serialization and deserialization of JSON using JSON-B. Here's the code:

package com.jsonbdemos;
import javax.json.bind.Jsonb;
import javax.json.bind.JsonbBuilder;
import javax.json.bind.JsonbConfig;

public class App {

    public static void main(String[] args) {
        Jsonb jsonb = JsonbBuilder.create(new JsonbConfig());
        String jsonData = "{\"creationDate\":\"2018-01-05\"}";

        // Create Widget object from JSON string.
        Widget widget = jsonb.fromJson(jsonData, Widget.class);
        System.out.println("JSON => object: " + widget.toString());

        // Serialize Widget object to JSON string.
        String jsonFromObject = jsonb.toJson(widget);
        System.out.println("object => JSON: " + jsonFromObject);
    }
}

package com.jsonbdemos;
import java.time.LocalDate;

public class Widget { // IllegalAccessException if "public" is removed.
    private LocalDate creationDate;
    public Widget() {}

    @Override
    public String toString() { return "creationDate=" + creationDate; }
    public LocalDate getCreationDate() { return creationDate; }
    public void setCreationDate(LocalDate creationDate) { this.creationDate = creationDate; }
}

There is a dependency for the latest version of the reference implementation of JSON-B (Eclipse Yasson) in pom.xml:

<dependency>
  <groupId>org.glassfish</groupId>
  <artifactId>javax.json</artifactId>
  <version>[1.1.2,)</version>
</dependency>
<dependency>
  <groupId>javax.json.bind</groupId>
  <artifactId>javax.json.bind-api</artifactId>
  <version>[1.0,)</version>
</dependency>
<dependency>
  <groupId>org.eclipse</groupId>
  <artifactId>yasson</artifactId>
  <version>[1.0.0,)</version>
</dependency>

The app runs fine, but if I change the access level of class Widget from public to nothing (i.e. "package private") an IllegalAccessException is thrown when calling Jsonb.fromJson():

Exception in thread "main" javax.json.bind.JsonbException: Can't create instance at org.eclipse.yasson.internal.ReflectionUtils.lambda$createNoArgConstructorInstance$1(ReflectionUtils.java:191) at java.base/java.security.AccessController.doPrivileged(Native Method) at org.eclipse.yasson.internal.ReflectionUtils.createNoArgConstructorInstance(ReflectionUtils.java:186) at org.eclipse.yasson.internal.serializer.ObjectDeserializer.getInstance(ObjectDeserializer.java:92) at org.eclipse.yasson.internal.serializer.AbstractContainerDeserializer.deserialize(AbstractContainerDeserializer.java:62) at org.eclipse.yasson.internal.Unmarshaller.deserializeItem(Unmarshaller.java:57) at org.eclipse.yasson.internal.Unmarshaller.deserialize(Unmarshaller.java:50) at org.eclipse.yasson.internal.JsonBinding.deserialize(JsonBinding.java:45) at org.eclipse.yasson.internal.JsonBinding.fromJson(JsonBinding.java:52) at com.jsonbdemos.App.main(App.java:15) Caused by: java.lang.IllegalAccessException: class org.eclipse.yasson.internal.ReflectionUtils cannot access a member of class com.jsonbdemos.Widget with modifiers "public" at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:361) at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:589) at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:479) at org.eclipse.yasson.internal.ReflectionUtils.lambda$createNoArgConstructorInstance$1(ReflectionUtils.java:189) ... 9 more

I don't see anything in the spec (JSR 367:"JSON-B: Java™ API for JSON Binding") (in section 3.7 Java Class) requiring a public class for deserialization.

Any suggestions on how to deserialize to a class instance which isn't public using JSON-B?

Update (5/2/18):

JSR 367 states that the "any instance passed to a deserialization operation must have a public or protected no-argument constructor", yet the same error also occurs if the constructor is protected rather than public.

I have reported that issue: Deserialization still not working with a protected no-arg constructor #118


Solution

  • I tested out a few variations of this with the following results:

    Standalone class (own source file):

    Static inner class:

    Non-static inner class:

    The key points from this are:

    1. Public and protected works, but package-protected or less does not work (both the class and the ctor must have equal or higher visibility than protected).
    2. Static inner classes have the same behavior as standalone classes
    3. Non-static inner classes are not accessible because they require an instance of the outer class to instantiate