4

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

rü-
  • 2,129
  • 17
  • 37
skomisa
  • 16,436
  • 7
  • 61
  • 102

1 Answers1

6

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

Standalone class (own source file):

  • class=public, ctor=public = SUCCESS
  • class=public, ctor=protected = SUCCESS
  • class=public, ctor=pkg-protected = ILLEGAL ACCESS
  • class=pkg-protected, ctor=public = ILLEGAL ACCESS

Static inner class:

  • class=public, ctor=public = SUCCESS
  • class=protected, ctor=public = SUCCESS
  • class=pkg-protected, ctor=public = ILLEGAL ACCESS
  • class=public, ctor=protected = SUCCESS
  • class=public, ctor=pkg-protected = ILLEGAL ACCESS

Non-static inner class:

  • class=public, ctor=public = ILLEGAL ACCESS

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
Andy Guibert
  • 41,446
  • 8
  • 38
  • 61
  • Unless I'm misunderstanding, bullet 3 is not quite right. I haven't tried, but binding a nested class from the outer class, where you necessarily have an instance of the outer class, should work. This is stated in [JSR 367 section "3.7.2 Nested Classes"](http://json-b.net/): "_Implementations MUST support the binding of public and protected nested classes..._". I'm accepting your answer anyway, since everything else conforms to the spec, and your answer got me to revisit the issue. I no longer get an exception. Presumably that was a bug in the earlier version of Yasson that I was using. – skomisa Dec 11 '18 at 00:14
  • FYI, I tried binding with a nested class. `Jsonb.toJson()` works, but `Jsonb.fromJson()` fails with _JsonbException: Can't create instance of a class: ... No default constructor found._ The reason `Jsonb` can't find the default constructor for the nested class even though I provided one [is given here](http://thecodersbreakfast.net/index.php?post/2011/09/26/Inner-classes-and-the-myth-of-the-default-constructor): _"even the default, no-arg constructor...becomes a one-arg constructor"_ for nested classes. Also see https://www.eclipse.org/forums/index.php/t/1086296/ – skomisa Dec 11 '18 at 05:33