openapi-generator: [BUG] [JavaSpring] Discriminator property gets serialized twice

openapi-generator version

3.3.4

Command line used for generation

I’m working with a JHipster API-First project, which uses openapi-generator-maven-plugin (v3.3.4).

Description

By following the OpenAPI 3.x documentation/examples, it seems that the property used as discriminator must also be specified in the properties list of the schema.

Example:

TicketEvent:
  type: object
  description: A generic event
  discriminator:
    propertyName: type
  required:
    - id
    - sequenceNumber
    - timestamp
    - type
  properties:
    id:
      type: integer
      format: int64
      readOnly: true
    sequenceNumber:
      type: integer
      readOnly: true
    timestamp:
      type: string
      format: date-time
      readOnly: true
    type:
      type: string
      readOnly: true
TicketMovedEvent:
  description: A ticket move event
  allOf:
    - $ref: '#/components/schemas/Event'
    - type: object
      required:
        - source
        - target
      properties:
        source:
          $ref: '#/components/schemas/Queue'
        target:
          $ref: '#/components/schemas/Queue'

The generated Java class for the code above seems to differ from the Jackson guidelines for polymorphic type handling with annotations, where the property used as discriminator must not be present in the class. The generated code also includes this property as a class attribute with getter/setter.

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

public class TicketEvent   {
   ...

   @JsonProperty("type")
   private String type;

This causes the serialization output to include the property twice, as shown below.

{
    ...
    "type": "TicketMovedEvent",
    "type": null,
    ...
}

I’ve also tried to remove the property from the OpenAPI properties list, leaving intact the discriminator part; this way the generated code corresponds to the Jackson’s guidelines and the serialization works just fine. On the other hand, I get the following error during the deserialization process.

com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "type" (class it.blutec.bludesk.web.api.model.TicketMovedEvent), not marked as ignorable ...

To fix this, i’ve configured the Jackson ObjectMapper object for ignoring this kind of situations.

ObjectMapper om = new ObjectMapper()
				.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)

By the way, this looks like a workaround to me beacuse my OpenAPI file doesn’t (strictly) follows the spec.

Suggest a fix

The generator should not include an additional attribute (with getter/setter) for a discriminator property.

About this issue

  • Original URL
  • State: open
  • Created 5 years ago
  • Reactions: 10
  • Comments: 31 (8 by maintainers)

Commits related to this issue

Most upvoted comments

@bonii-xx that’s exactly what I’m doing with 4.2.2 (or 4.2.3).

Actually, omitting the discriminator property in the properties list is an “hack”. The OpenAPI 3.x spec clearly states:

…you can add the discriminator/propertyName keyword to model definitions. This keyword points to the property that specifies the data type name…

I don’t know why they accepted the EXISTING_PROPERTY pull request, it seems to me that this project lacks of supervision.

IMHO explicitely defining the discriminator property is a must, because at a certain point in time an object must be serialized to JSON (or XML) and, without an additional property valued with the class name, the class information is lost. Thus the need for specifying in which key (property) expose this info (string).

We tried to update from 4.2.3 to 4.3.1, and I stumbled upon this issue. Our serialization is now broken, because the discriminator field is missing. Before everything worked fine.

We’re using this approach, which worked fine in 4.2.3. (no field generated in the java code)

    Foo:
      type: object
      properties:
        label:
          type: string
      discriminator:
        propertyName: type
        mapping:
          bar: '#/components/schemas/Bar'
          baz: '#/components/schemas/Baz'

With the PR that got released with 4.3.0 we now have to explicitly define the discriminator property? Is this intended?

Version 4.3.0 broke my clients too. The include change of JsonTypeInfo.As.PROPERTY to JsonTypeInfo.As.EXISTING_PROPERTY broken them.

When I instantiate a new object of a sub-type class, the discriminator doesn’t exists in this object with the new strategy.

A workaround to this is add the discriminator as property in the POJO class, but in this way the client will have to fill this property when instantiate a object. I really dislike this workaround, to me the old strategy is better, and if the change is necessary, add a option (configOption) to change the include type of serialization is more convenient and attend the two cases.

I just noticed this was for Spring - this is an issue for the jaxrs-spec generator also.

This is quite frustrating. IMO the solution needs to be to remove the discriminator property from the generated POJO altogether. The whole purpose of polymorphic types in this case is to encode the type info into the class itself. Having the discriminator property on the class just introduces the possibility that it be set such that it disagrees with the actual class of the object.

Manually removing the attribute (plus getter/setter), corresponding to the discriminator property, from the generated class does the trick. As stated above, I’ve just needed to turn off the DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES configuration on the ObjectMapper.

I’ve also tried to use JsonTypeInfo.As.EXISTING_PROPERTY in the discriminator’s related annotation, but with no success.

I think that the type property only makes sense in the serialized version of an object; as a matter of fact, it conveys what in OO languages is modeled as classes. So I don’t see the point in having an attribute in the class representing the class itself.

@nickshoe thanks for clarifying this for me.

@amir-ba, please read this: https://github.com/OpenAPITools/openapi-generator/issues/3796#issuecomment-627874116.

TL;DR: EXISTING_PROPERTY is intended to be used only for deserialization purposes.

Just wanted to share my workaround for this (not great…but it’s working until this can be fixed properly). This is specifically for folks using Spring Boot 2, but I’m sure will help other Spring and even non-Spring Java project maintainers:

@Configuration
public class JacksonConfiguration {
    @Bean
    Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() {
        return builder -> {
            var m = new SimpleModule();
            m.setSerializerModifier(new BeanSerializerModifier() {
                @Override
                public List<BeanPropertyWriter> changeProperties(
                        SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties
                ) {
                    for (var bpWriter : beanProperties) {
                        if ("_object_type".equals(bpWriter.getName())) {
                            bpWriter.assignNullSerializer(new ObjectTypeNullSerializer());
                        }
                    }
                    return super.changeProperties(config, beanDesc, beanProperties);
                }
            });
            builder.modulesToInstall(m);
        };
    }

    static class ObjectTypeNullSerializer extends JsonSerializer<Object> {
        @Override
        public void serialize(
                Object value, JsonGenerator gen, SerializerProvider serializers
        ) throws IOException {
            gen.writeString(gen.getCurrentValue().getClass().getSimpleName());
        }
    }
}

This assumes a fairly standard model inheritance e.g.:

    FooModelBase:
      type: object
      required:
        - _object_type
      properties:
        _object_type:
          type: string
      discriminator:
        propertyName: _object_type
    FooModelChildOne:
      allOf:
        - $ref: "#/components/schemas/FooModelBase"
        - type: object
          properties:
            bar:
              type: string
            baz:
              type: string
    FooModelChildTwo:
      allOf:
        - $ref: "#/components/schemas/FooModelBase"
        - type: object
          properties:
            zig:
              type: string
            zag:
              type: string

I have the same problems as mentioned above: With version 5.0.0, the plain generated code:

  • If i define the discriminator field <discriminator_field> explicitly in the spec itis not being filled automatically with the name of the class. Rather it is null.
  • When i do not define <discriminator_field> in the spec i get no type information.
  • When i (in the generated code) manually replace “@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = “type”, visible = true)” with “@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY)” it works and produces a json which includes “@type”:“rectangle”. With that i can serialize/deserialize. (This is from my post over at stackoverflow: https://stackoverflow.com/questions/65701626/jackson-not-generating-type-information?noredirect=1)

Anyway, right now i generate the code, change the code as mentioned above and commit it (which i would like to avoid).

Hi

I have the same problem and it seems very important to me.

Will it be solved in version 5.0.0?

@bonii-xx the two issues are related, please read all the comments.

@bkabrda it seems like the approach does work, but the change from PROPERTY to EXISTING_PROPERTY needs to be made for other generators as well, for example JavaSpring, which we are using. I’ll created a PR for this shortly.

If removing discriminator property addresses the issue, we can certainly do that.