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

[java] Polymorphic deserialization not working for sub-subtype #827

Open
aduval opened this issue Aug 16, 2018 · 1 comment
Open

[java] Polymorphic deserialization not working for sub-subtype #827

aduval opened this issue Aug 16, 2018 · 1 comment

Comments

@aduval
Copy link

aduval commented Aug 16, 2018

Description

Cannot deserialize an object when using its super-supertype.

openapi-generator version

3.2.1 Not a regression. Same issue when using Swagger 2.

OpenAPI declaration file content or url
openapi: 3.0.0
info:
  version: 1.0.0
  title: Service API
  contact: {}
paths:
  '/getPolymorphic':
    get:
      description: ''
      operationId: getPolymorphic
      responses:
        '200':
          description:
            "Get an Ancestor, GrandParent or Parent"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Ancestor'
components:
  schemas:
    Ancestor:
      type: object
      required:
        - type
      discriminator:
        propertyName: type
      properties:
        type:
          type: string
    GrandParent:
      allOf:
        - $ref: '#/components/schemas/Ancestor'
        - type: object
    Parent:
      allOf:
        - $ref: '#/components/schemas/GrandParent'
        - type: object
Command line used for generation

Using the maven plugin with hideGenerationTimestamp=true
Using the default okhttp-gson client

Steps to reproduce

These two tests fail but I expected them to work:

public class AppTest extends TestCase {

	public void testParentAsAncestor() {
		var json = new JSON();
		Parent parent = new Parent();        
		parent.setType("Parent");
        
		var jsonString = json.serialize(parent);
		Ancestor parentAsAncestor = json.deserialize(jsonString, Ancestor.class);
		assertEquals("Parent", parentAsAncestor.getType());
		assertTrue(parentAsAncestor instanceof Parent);
	}

	public void testParentAsGrandParent() {
		var json = new JSON();
		Parent parent = new Parent();      
		parent.setType("Parent");
        
		var jsonString = json.serialize(parent);
		GrandParent parentAsGrandParent = json.deserialize(jsonString, GrandParent.class);
		assertEquals("Parent", parentAsGrandParent.getType());
		assertTrue(parentAsGrandParent instanceof Parent);
	}
}

Stack trace:

java.lang.IllegalArgumentException: cannot determine model class of name: <Parent>
	at com.mycompany.JSON.getClassByDiscriminator(JSON.java:82)
	at com.mycompany.JSON.access$100(JSON.java:43)
	at com.mycompany.JSON$1.getClassForElement(JSON.java:60)
	at io.gsonfire.gson.TypeSelectorTypeAdapterFactory$TypeSelectorTypeAdapter.read(TypeSelectorTypeAdapterFactory.java:64)
	at io.gsonfire.gson.NullableTypeAdapter.read(NullableTypeAdapter.java:36)
	at com.google.gson.TypeAdapter.fromJsonTree(TypeAdapter.java:285)
	at io.gsonfire.gson.FireTypeAdapter.deserialize(FireTypeAdapter.java:82)
	at io.gsonfire.gson.FireTypeAdapter.read(FireTypeAdapter.java:52)
	at com.google.gson.Gson.fromJson(Gson.java:887)
	at com.google.gson.Gson.fromJson(Gson.java:852)
	at com.google.gson.Gson.fromJson(Gson.java:801)
	at com.mycompanye.JSON.deserialize(JSON.java:149)
	at com.mycompany.Test.testParentAsAncestor(Test.java:47)
Suggest a fix/enhancement

Didn't have time to setup yet (just started using openapi generator). I could fix it but will need guidance.

The current generated code in JSON.java:

    public static GsonBuilder createGson() {
        GsonFireBuilder fireBuilder = new GsonFireBuilder()
          .registerTypeSelector(Ancestor.class, new TypeSelector() {
            @Override
            public Class getClassForElement(JsonElement readElement) {
                Map classByDiscriminatorValue = new HashMap();
                classByDiscriminatorValue.put("GrandParent".toUpperCase(), GrandParent.class);
                classByDiscriminatorValue.put("Ancestor".toUpperCase(), Ancestor.class);
                return getClassByDiscriminator(
                                           classByDiscriminatorValue,
                                           getDiscriminatorValue(readElement, "type"));
            }
          })
        
        ;
        GsonBuilder builder = fireBuilder.createGsonBuilder();
        return builder;
    }

but I would expect exactly this:

public static GsonBuilder createGson() {
        GsonFireBuilder fireBuilder = new GsonFireBuilder()
          .registerTypeSelector(Ancestor.class, new TypeSelector() {
            @Override
            public Class getClassForElement(JsonElement readElement) {
                Map classByDiscriminatorValue = new HashMap();
                classByDiscriminatorValue.put("Parent".toUpperCase(), Parent.class);
                classByDiscriminatorValue.put("GrandParent".toUpperCase(), GrandParent.class);
                classByDiscriminatorValue.put("Ancestor".toUpperCase(), Ancestor.class);
                return getClassByDiscriminator(
                                           classByDiscriminatorValue,
                                           getDiscriminatorValue(readElement, "type"));
            }
          })
          .registerTypeSelector(GrandParent.class, new TypeSelector() {
              @Override
              public Class getClassForElement(JsonElement readElement) {
                  Map classByDiscriminatorValue = new HashMap();
                  classByDiscriminatorValue.put("Parent".toUpperCase(), Parent.class);
                  classByDiscriminatorValue.put("GrandParent".toUpperCase(), GrandParent.class);
                  return getClassByDiscriminator(
                                             classByDiscriminatorValue,
                                             getDiscriminatorValue(readElement, "type"));
              }
            })        
        ;
        GsonBuilder builder = fireBuilder.createGsonBuilder();
        return builder;
    }
@jmini
Copy link
Member

jmini commented Aug 17, 2018

Thank you a lot for this detailed issue.

The code that creates the code is here:

public static GsonBuilder createGson() {
GsonFireBuilder fireBuilder = new GsonFireBuilder()
{{#models}}{{#model}}{{#discriminator}} .registerTypeSelector({{classname}}.class, new TypeSelector() {
@Override
public Class getClassForElement(JsonElement readElement) {
Map classByDiscriminatorValue = new HashMap();
{{#mappedModels}}
classByDiscriminatorValue.put("{{mappingName}}".toUpperCase(), {{modelName}}.class);
{{/mappedModels}}
classByDiscriminatorValue.put("{{classname}}".toUpperCase(), {{classname}}.class);
return getClassByDiscriminator(
classByDiscriminatorValue,
getDiscriminatorValue(readElement, "{{{propertyName}}}"));
}
})
{{/discriminator}}{{/model}}{{/models}}

But I think that the problem is that for the moment the Codegen layer (*) do not consider that GrandParent has a discriminator. So my take is this is what needs to be solved.

I can help you to file a PR if you want. If you need additional pointers, please let me know.

(*) after the openapi-spec is parsed, the Codegen layer is an other model that contains all the values that the templates will use.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants