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] discriminator.mapping is not supported (in generated model) #417

Open
0v1se opened this issue Jun 29, 2018 · 25 comments
Open

[JAVA] discriminator.mapping is not supported (in generated model) #417

0v1se opened this issue Jun 29, 2018 · 25 comments

Comments

@0v1se
Copy link
Contributor

0v1se commented Jun 29, 2018

Description

spec: https://swagger.io/docs/specification/data-models/inheritance-and-polymorphism/
mapping can be specified in discriminator:

discriminator:
  propertyName: petType
  mapping:
    dogggg: '#/components/schemas/Dog'
    catttt: '#/components/schemas/Cat'

Currently it's not taken in account when generating model (it's not for Java, may be for every other language too)

openapi-generator version

3.1.0-SNAPSHOT

OpenAPI declaration file content or url
openapi: "3.0.0"
info:
  version: 1.0.0
  title: Swagger Petstore
  license:
    name: MIT
servers:
  - url: http://petstore.swagger.io/v1
paths:
  /pets:
    get:
      summary: List all pets
      operationId: listPets
      tags:
        - pets
      parameters:
        - name: limit
          in: query
          description: How many items to return at one time (max 100)
          required: false
          schema:
            type: integer
            format: int32
      responses:
        '200':
          description: A paged array of pets
          headers:
            x-next:
              description: A link to the next page of responses
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Pets"
        default:
          description: unexpected error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    post:
      summary: Create a pet
      operationId: createPets
      tags:
        - pets
      responses:
        '201':
          description: Null response
        default:
          description: unexpected error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /pets/{petId}:
    get:
      summary: Info for a specific pet
      operationId: showPetById
      tags:
        - pets
      parameters:
        - name: petId
          in: path
          required: true
          description: The id of the pet to retrieve
          schema:
            type: string
      responses:
        '200':
          description: Expected response to a valid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Pets"
        default:
          description: unexpected error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
components:
  schemas:
    Pet:
      type: object
      discriminator:
        propertyName: petType
        mapping:
          dogggg: '#/components/schemas/Dog'
          catttt: '#/components/schemas/Cat'
      properties:
        name:
          type: string
        petType:
          type: string
      required:
      - name
      - petType
    Cat:
      description: A representation of a cat
      allOf:
      - $ref: '#/components/schemas/Pet'
      - type: object
        properties:
          huntingSkill:
            type: string
            description: The measured skill for hunting
            enum:
            - clueless
            - lazy
            - adventurous
            - aggressive
        required:
        - huntingSkill
    Dog:
      description: A representation of a dog
      allOf:
      - $ref: '#/components/schemas/Pet'
      - type: object
        properties:
          packSize:
            type: integer
            format: int32
            description: the size of the pack the dog is from
            default: 0
            minimum: 0
        required:
        - packSize
    Pets:
      type: array
      items:
        $ref: "#/components/schemas/Pet"
    Error:
      required:
        - code
        - message
      properties:
        code:
          type: integer
          format: int32
        message:
          type: string
Command line used for generation

java -jar ... generate -i api.yaml -g java --library resttemplate

Steps to reproduce

Generate model
Here is what's generated in Pet.java:

@JsonSubTypes({
  @JsonSubTypes.Type(value = Cat.class, name = "Cat"),
  @JsonSubTypes.Type(value = Dog.class, name = "Dog"),
})
Related issues/PRs

Related to #197

Suggest a fix/enhancement

Should generate model like this:

@JsonSubTypes({
  @JsonSubTypes.Type(value = Cat.class, name = "catttt"),
  @JsonSubTypes.Type(value = Dog.class, name = "dogggg"),
})
@jmini
Copy link
Member

jmini commented Jun 29, 2018

According to you having the name in the @JsonSubTypes.Type annotation should be sufficient to make the JSON-Deserializer work?

Where is the information about the field name (petType in your example) that is used to determinate which kind of object should be created?

@0v1se
Copy link
Contributor Author

0v1se commented Jun 29, 2018

@jmini no, it's not sufficient
Also @JsonTypeInfo should be present:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "petType", visible = true )

I didn't notice it because it's generated correctly.

@0v1se
Copy link
Contributor Author

0v1se commented Jun 29, 2018

One more thing, petType property is also generated in the Pet model.

  @JsonProperty("petType")
  private String petType = null;

This way wrong json is produced:

{"petType":"Dog","name":null,"petType":null,"packSize":10}

@0v1se
Copy link
Contributor Author

0v1se commented Jul 11, 2018

@jmini I can fix this and change jackson annotations to

@JsonSubTypes({
  @JsonSubTypes.Type(value = Cat.class, name = "catttt"),
  @JsonSubTypes.Type(value = Dog.class, name = "dogggg"),
})

Also, I can remove property identifying type (in my example is 'petType') from the pojo.
I think it won't break anything.

What do you think?
This way basic polymorphism will be supported at least for Java clients

@jmini
Copy link
Member

jmini commented Jul 11, 2018

Yes, I have started to work on it:
fix_discriminator. I have introduced a DiscriminatorCodegen used in CodegenModel and in CodegenOperation. This is a drop in replacement for io.swagger.v3.oas.models.media.Discriminator that was used before.

This is work in progress, but my Idea was to edit the templates so that the cases described here are covered.

@0v1se
Copy link
Contributor Author

0v1se commented Jul 11, 2018

@0v1se
Copy link
Contributor Author

0v1se commented Jul 11, 2018

I know it's WIP, but a small note:
equals should be implemented for DiscriminatorCodegen, it's used in the code

@jmini
Copy link
Member

jmini commented Jul 11, 2018

Thank you a lot for this feedback.

And, mappedModels will be used insted of children

Yes this was my idea.

And an other template change is needed to remove the creation of the discriminator as field:

  @JsonProperty("petType")
  private String petType = null;

(did not investigate yet)

So, this method (modelInheritanceSupportInGson) won't be needed?

Nice catch, I did not analyze who was computing the "children" map.

equals should be implemented for DiscriminatorCodegen, it's used in the code

Nice catch!


Do you want to take over the PR? I have not that much time right now.
If you finish it, I will review it.


There is also a small spec modules/openapi-generator/src/test/resources/3_0/allOf.yaml to test that everything is working well. A small unit test in DefaultCodegenTest would be great. Minimum would be to parse the spec and to ensure that the discriminator is set in the CodegenOperation.

@0v1se
Copy link
Contributor Author

0v1se commented Jul 11, 2018

I will try, but I'm not very comfortable with the code right now (I mean, not very familiar with).
Also, I won't be able to implement this feature for other languages/libraries.

Will get back if I have questions.

@jmini
Copy link
Member

jmini commented Jul 11, 2018

I won't be able to implement this feature for other languages/libraries.

This is always the case. I just fix stuff in for the Java generators and I help at model level (the codegen classes that are exposed in the templates). Then I consider that people with knowledge of other languages needs to consume the elements in their models.

I can help you if you need to know something, but from the feedback you gave me on my WIP commit, I think you know enough to create a great PR.

@0v1se
Copy link
Contributor Author

0v1se commented Jul 11, 2018

I almost finished. Will submit PR soon (I had only to change templates and do some minor stuff).
But it won't cover one aspect: discriminator field won't be removed from the model.

It is a little bit hard to do this. I will write some thoughts after I submit PR

@jmini
Copy link
Member

jmini commented Jul 11, 2018

Sure feel free to do stuff in multiple small steps...

@0v1se
Copy link
Contributor Author

0v1se commented Jul 11, 2018

@jmini
submitted #536

some concerns about the PR:

  1. Deleted modelInheritanceSupportInGson cause it doesn't do anything besides calculating children (for annotation) - not 100% sure about this. Pls, review
  2. Removing discriminator field requires more information. Will add later

@0v1se
Copy link
Contributor Author

0v1se commented Jul 11, 2018

Regarding not including discriminator field from model (petType from this issue)

  1. According to https://swagger.io/docs/specification/data-models/inheritance-and-polymorphism/ this property should be defined for every type (Pet, Dog, Cat). Why do we need it in the first place? Is it code duplication? If I define discriminator property, it's obvious I have this property. No need to add it to the model again. Don't you think?
  2. If we want to not include this property in generated classes, then there are some strategies:
    2.1 remove it from CodegenModel.vars (currently I think it's best solution, but I can't see all the consequences of this).
    2.2 flag the property with isDiscriminator and filter it out in templates
    2.3 create one more collection with properties and use it in pojo.mustache (and create hasMore, hasVars analogs for this collection)

I tried to implement 2.2, but it's not that easy: I needed to change hasMore, hasVars and possibly other auxiliary fields from CodegenModel to correctly generate java files.

What do you think about this?

@jmini jmini added this to the 3.2.0 milestone Jul 18, 2018
@jmini
Copy link
Member

jmini commented Jul 18, 2018

I will come back to you with the problem I see with the discriminator in the properties. I am no longer sure what we need.

I will give you an example that is not working at all (using getClass().getSimpleName() as value). We need to fix it.


In the mean time, I think it is important to document changes like this in the migration guide, I have opened #587 for that.

@wing328 wing328 modified the milestones: 3.2.0, 3.2.1 Aug 6, 2018
@wing328 wing328 modified the milestones: 3.2.1, 3.2.2 Aug 14, 2018
@jmini
Copy link
Member

jmini commented Aug 14, 2018

I have some cases that produce compile error when prefixes are used (all my models are using a common prefix). I will document this.

Other interesting case, @yuanpli reported a compile error when an enum is used: #806

@wing328 wing328 modified the milestones: 3.2.2, 3.2.3 Aug 22, 2018
@wing328 wing328 modified the milestones: 3.2.3, 3.3.0 Aug 30, 2018
@wing328 wing328 modified the milestones: 3.3.0, 3.3.1 Oct 1, 2018
@wing328 wing328 removed this from the 3.3.1 milestone Oct 15, 2018
@wing328 wing328 added this to the 3.3.4 milestone Nov 14, 2018
@wing328 wing328 modified the milestones: 3.3.4, 4.0.0 Nov 30, 2018
@wing328 wing328 modified the milestones: 4.0.0, 4.0.1 May 13, 2019
@wing328 wing328 modified the milestones: 4.0.1, 4.0.2 May 31, 2019
@wing328 wing328 modified the milestones: 4.0.2, 4.0.3 Jun 20, 2019
@wing328 wing328 modified the milestones: 4.0.3, 4.1.0 Jul 9, 2019
@wing328 wing328 modified the milestones: 4.1.0, 4.1.1 Aug 9, 2019
@wing328 wing328 modified the milestones: 4.1.1, 4.1.2 Aug 26, 2019
@nickshoe
Copy link

The fix for this may also be needed for Java Spring target?
Maybe related to #3796

@wing328 wing328 modified the milestones: 4.1.2, 4.1.3 Sep 11, 2019
@ivarreukers
Copy link

ivarreukers commented Sep 30, 2019

According to the status of the PR, I assume that this has been fixed since 3.2.x correct?

Because I'm still facing the same issue when generating from swagger. Although one difference is that my discriminator is in the definition. (And in the definitions, it is only allowed to be a string value)

I have:

definitions:
  Instruction:
    type: "object"
    discriminator: "instructionType"
    properties:
      instructionType:
        description: "Type of instruction"
        enum:
          - "EMAIL"
          - "SOME_OTHER"
  EmailInstruction:
    allOf:
      - $ref: "#/definitions/Instruction"
      - type: "object"
        properties:
          name:
            type: "string"
            example: "Name of reciever"
            description: "Name of the receiver"
          emailAddress:
            type: "string"
            example: "info@stackoverflow.com"
            description: "Email address of the receiver"
  SomeOtherInstruction:
    allOf:
      - $ref: "#/definitions/Instruction"
      - type: "object"
        properties:
          anotherone:
            type: "string"
            example: "Name of reciever"
            description: "Name of the receiver"

And in turn the Instruction is wrapped in another object. When generating, it still uses the class name for the JsonSubType annotation:

Actual:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "instructionType", visible = true)
@JsonSubTypes({
  @JsonSubTypes.Type(value = SomeOtherInstruction.class, name = "SomeOtherInstruction"),
  @JsonSubTypes.Type(value = EmailInstruction.class, name = "EmailInstruction"),
})

Expected:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "instructionType", visible = true)
@JsonSubTypes({
  @JsonSubTypes.Type(value = SomeOtherInstruction.class, name = "SOME_OTHER"),
  @JsonSubTypes.Type(value = EmailInstruction.class, name = "EMAIL"),
})

@wing328 wing328 modified the milestones: 4.1.3, 4.2.0 Oct 4, 2019
@wing328 wing328 removed this from the 4.2.0 milestone Oct 30, 2019
@davinchia
Copy link

This is also biting me with the following spec and 4.2.2:

OutputFormat:
      type: object
      required:
        - type
      properties:
        type:
          type: string
      discriminator:
        propertyName: type
        mapping:
          outputKeyValueLabel: '#/components/schemas/OutputKeyValueLabel'
          outputIdLabel: '#/components/schemas/OutputIdLabel'
    OutputKeyValueLabel:
      allOf:
        - $ref: '#/components/schemas/OutputFormat'
        - type: object
          properties:
            keyLabel:
              type: string
              example: PETS
            valueLabel:
              type: string
              example: DOGS
          required:
            - keyLabel
    OutputIdLabel:
      allOf:
        - $ref: '#/components/schemas/OutputFormat'
        - type: object
          properties:
            idLabel:
              type: string
              example: MM1234
          required:
            - idLabel

this gives

public class OutputFormat {
  public static final String SERIALIZED_NAME_TYPE = "type";
  @SerializedName(SERIALIZED_NAME_TYPE)
  private String type;

  public OutputFormat() {
    this.type = this.getClass().getSimpleName();
  }

  public OutputFormat type(String type) {
    
    this.type = type;
    return this;
  }
...

and the discriminator is set to the class's name instead of the specified mapping. There also isn't any JsonTypeInfo annotations on the classes.

@ivarreukers
Copy link

Note that if you upgrade to an OpenAPI specification instead of swagger 2.0 and use f.e. the <library>jersey2</library> then it does work. Also check out this stackoverflow post: https://stackoverflow.com/questions/59252417/generate-a-property-as-schema-definition-in-openapi-3-0

@davinchia
Copy link

Might be mistaken - this should be on OpenApi 3.0.

Silly me. The JsonTypeInfo annotations are Jackson, while the default uses Gson, which has terrible polymorphic support and probably explains why this is misbehaving. Confirm changing to Jersey2 fixed it. Thanks @ivarreukers!

dlnilsson added a commit to appgate/terraform-provider-appgatesdp that referenced this issue Sep 16, 2020
openapi.generator does not play well with discriminator(s) from the openapi spec.

related issues:
OpenAPITools/openapi-generator#4559
OpenAPITools/openapi-generator#417

the  api_ldap_identity_providers.go is a  copy/paste search and replace of api_identity_providers.go
to return LdapProviders instead of sub stub IdentityProvider

we need to do the same for the identity provider types.

Signed-off-by: Daniel Nilsson <daniel.nilsson@appgate.com>
@alqh
Copy link

alqh commented Oct 10, 2021

Just looking at some examples in this conversation that worked (e.g @ivarreukers example), do we really need a self reference from the child to the parent (thus creating that dependency between discriminator and its implementation classes)

According to the example of discriminator in inheritance and polymorphism, the following should work (without cyclic reference between child back to parent)

parent:
      oneOf:
        - $ref: '#/components/schemas/simpleObject'
        - $ref: '#/components/schemas/complexObject'
      discriminator:
        propertyName: objectType
        mapping:
          Simple: '#/components/schemas/simpleObject'
          Complex: '#/components/schemas/complexObject'
    
simpleObject:
      type: object
      required:
        - objectType
      properties:
        objectType:
          type: string

complexObject:
      type: object
      required:
        - objectType
      properties:
        objectType:
          type: string

This generated the right JsonSubTypes on the parent


@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "objectType", visible = true)
@JsonSubTypes({
  @JsonSubTypes.Type(value = SimpleObjectDto.class, name = "Simple"),
  @JsonSubTypes.Type(value = ComplexObjectDto.class, name = "Complex"),
})
@com.fasterxml.jackson.annotation.JsonIgnoreProperties(ignoreUnknown=true)

public class ParentDto   { .... }

but the child does not have the extension to the parent

public class SimpleObjectDto { ... }  // missing extends ParentDto

@lostiniceland
Copy link
Contributor

lostiniceland commented May 30, 2022

but the child does not have the extension to the parent

Thats because of #8495

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

No branches or pull requests

8 participants