Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Polymorphic Deserialization loses type information when subtypes of subtypes are added #544

Closed
njr-11 opened this issue Apr 6, 2022 · 2 comments · Fixed by #546
Closed
Assignees
Labels
bug Something isn't working right

Comments

@njr-11
Copy link
Contributor

njr-11 commented Apr 6, 2022

Describe the bug
When trying out the new polymorphic deserialization feature that is new in JSON-B 3.0 on the Yasson 3.0.0-RC1 JSON-B provider, I noticed that it worked well in the basic scenario of a type with subtypes, but when more subtypes were added to a subtype, then the first-level subtype itself lost its @type property in the generated JSON, causing it to be unable to deserialize. In the following test output, you can see that the third list element lacks a "@type":"area" in its JSON, and then the error which goes along with that,

Converted list of locations to JSON as:
[{"@type":"area","@area":"city","name":"Some City","population":2000,"state":"Some State"},
{"@type":"area","@area":"state","name":"Some State","population":1000000,"capital":"Some Capital City"},
{"name":"North America","population":600000000}]
Apr 06, 2022 11:19:54 AM org.eclipse.yasson.internal.DeserializationContextImpl deserializeItem
SEVERE: Cannot infer a type for unmarshalling into: TestPolymorphism$Location
Exception in thread "main" jakarta.json.bind.JsonbException: Cannot infer a type for unmarshalling into: TestPolymorphism$Location
	at org.eclipse.yasson.internal.deserializer.DefaultObjectInstanceCreator.<init>(DefaultObjectInstanceCreator.java:41)
	at org.eclipse.yasson.internal.deserializer.DeserializationModelCreator.createObjectDeserializer(DeserializationModelCreator.java:251)
	at org.eclipse.yasson.internal.deserializer.DeserializationModelCreator.deserializerChainInternal(DeserializationModelCreator.java:193)
	at org.eclipse.yasson.internal.deserializer.DeserializationModelCreator.deserializerChain(DeserializationModelCreator.java:135)
	at org.eclipse.yasson.internal.deserializer.DeserializationModelCreator.createNewChain(DeserializationModelCreator.java:487)
	at org.eclipse.yasson.internal.deserializer.DeserializationModelCreator.typeProcessor(DeserializationModelCreator.java:476)
	at org.eclipse.yasson.internal.deserializer.DeserializationModelCreator.typeProcessor(DeserializationModelCreator.java:429)
	at org.eclipse.yasson.internal.deserializer.DeserializationModelCreator.createArrayDeserializer(DeserializationModelCreator.java:333)
	at org.eclipse.yasson.internal.deserializer.DeserializationModelCreator.deserializerChainInternal(DeserializationModelCreator.java:187)
	at org.eclipse.yasson.internal.deserializer.DeserializationModelCreator.deserializerChain(DeserializationModelCreator.java:135)
	at org.eclipse.yasson.internal.deserializer.DeserializationModelCreator.deserializerChain(DeserializationModelCreator.java:123)
	at org.eclipse.yasson.internal.DeserializationContextImpl.deserializeItem(DeserializationContextImpl.java:141)
	at org.eclipse.yasson.internal.DeserializationContextImpl.deserialize(DeserializationContextImpl.java:131)
	at org.eclipse.yasson.internal.JsonBinding.deserialize(JsonBinding.java:55)
	at org.eclipse.yasson.internal.JsonBinding.fromJson(JsonBinding.java:62)
	at TestPolymorphism.main(TestPolymorphism.java:69)

I found that you can work around this by awkwardly declaring the subtype to be a subtype of itself,

    @JsonbTypeInfo(key = "@area", value = {
        ...
        @JsonbSubtype(alias = "general", type = Area.class)
    })
    public static class Area implements Location {

in which case the @type gets added back in,

{"@type":"area","@area":"general","name":"North America","population":600000000}

but that should not be necessary.

To Reproduce
Run this test case:

import jakarta.json.bind.*;
import jakarta.json.bind.annotation.*;
import java.util.*;

public class TestPolymorphism {

    @JsonbTypeInfo({
        @JsonbSubtype(alias = "point", type = Point.class),
        @JsonbSubtype(alias = "area", type = Area.class)
    })
    public static interface Location {
    }

    @JsonbTypeInfo(key = "@area", value = {
        @JsonbSubtype(alias = "city", type = City.class),
        @JsonbSubtype(alias = "state", type = State.class),

        // Workaround here - It is awkward that Area must include itself
        // as a subtype of itself, but if we don't do this, Yasson also
        // omits "@type": "area" from the JSON, making it unable to
        // deserialize as a Location.class.
        // --- Comment out the following annotation to make it fail ---
        //@JsonbSubtype(alias = "general", type = Area.class)
    })
    public static class Area implements Location {
        public String name;
        public long population;
    }

    public static class City extends Area {
        public String state;
    }

    public static class State extends Area {
        public String capital;
    }

    public static class Point implements Location {
        public double x;
        public double y;
    }

    public static void main(String[] args) throws Exception {
        try (Jsonb jsonb = JsonbBuilder.create()) {
            Location[] list = new Location[3];

            City city = new City();
            city.name = "Some City";
            city.population = 2000;
            city.state = "Some State";
            list[0] = city;

            State state = new State();
            state.name = "Some State";
            state.population = 1000000;
            state.capital = "Some Capital City";
            list[1] = state;

            Area northAmerica = new Area();
            northAmerica.name = "North America";
            northAmerica.population = 600000000;
            list[2] = northAmerica;

            String json = jsonb.toJson(list);

            System.out.println("Converted list of locations to JSON as:");
            System.out.println(json);

            Location[] deserialized = jsonb.fromJson(json, Location[].class);

            City c = (City) deserialized[0];
            assertEquals(city.name, c.name);
            assertEquals(city.population, c.population);
            assertEquals(city.state, c.state);

            State s = (State) deserialized[1];
            assertEquals(state.name, s.name);
            assertEquals(state.population, s.population);
            assertEquals(state.capital, s.capital);

            Area a = (Area) deserialized[2];
            assertEquals(northAmerica.name, a.name);
            assertEquals(northAmerica.population, a.population);
        }
    }

    private static final void assertEquals(Object expected, Object observed) {
        if (!Objects.equals(expected, observed))
            throw new AssertionError("Expected " + expected + ", but observed " + observed);
    }
}

Expected behavior
The third list element of the test output JSON array (without the workaround enabled) should look like this, including @type,

{"@type":"area","name":"North America","population":600000000}

and then it would deserialize properly because the necessary type information would be found there.

System information:

  • OS: Mac
  • Java Version: 11
openjdk version "11.0.2" 2019-01-15
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.2+9)
Eclipse OpenJ9 VM AdoptOpenJDK (build openj9-0.12.1, JRE 11 Mac OS X amd64-64-Bit Compressed References 20190204_123 (JIT enabled, AOT enabled)
OpenJ9   - 90dd8cb40
OMR      - d2f4534b
JCL      - 289c70b684 based on )
  • Yasson Version: 3.0.0-RC1

Additional context
Add any other context about the problem here.

@njr-11 njr-11 added the bug Something isn't working right label Apr 6, 2022
@Verdent Verdent self-assigned this Apr 13, 2022
@Verdent
Copy link
Member

Verdent commented Apr 13, 2022

Thank you, I will take a look at that :-)

@Verdent
Copy link
Member

Verdent commented Apr 13, 2022

Very nice example. It was easy to see the issue. I will tune Yasson it a bit to reflect the following situations:

  1. Serialized instance is type of Area -> it will contain just the type information from its parents and not area information itself
  2. Serialized instance is type of Village and not present in Area configuration -> no polymorphic info included.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working right
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants