openapi-generator: [BUG][Java][Spring] openapiNullable - JsonNullable usage is based on nullable property instead of required property

Bug Report Checklist

  • Have you provided a full/minimal spec to reproduce the issue?
  • Have you validated the input using an OpenAPI validator (example)?
  • Have you tested with the latest master to confirm the issue still exists?
  • Have you searched for related issues/PRs?
  • What’s the actual output vs expected output?
  • [Optional] Sponsorship to speed up the bug fix or feature request (example)
Description

In Java CodeGeneration openapiNullable is used to determine if the special type JsonNullable should be used or not. The description of this type from the original documentation is as follows:

The JsonNullable wrapper shall be used to wrap Java bean fields for which it is important to distinguish between an explicit “null” and the field not being present. A typical usage is when implementing Json Merge Patch where an explicit "null"has the meaning “set this field to null / remove this field” whereas a non-present field has the meaning “don’t change the value of this field”.

So, in a request body (especially in PATCH requests) it is utilized to signalize, if a specifc field was specified in the body or not. This can be used to just ignore any change to the property and keep the old value or to apply a new value to it.

BUT. The current implementation is using the nullable property of a field instead of the required property. So nullable should actually tell about if a field can be null or not IF it is specified. nullable tells nothing about if a field must be present or can be omitted. This is what required is for. required declares, if a field as to be present or not, independent of if it may be nullable or not.

The fix would be to swap nullable and required for the usage of JsonNullable.

openapi-generator version

6.4.0, but goes back until 5.6.0 minimum (probably since openapiNullable was implemented first).

OpenAPI declaration file content or url

Existing /3_0/petstore-with-nullable-required.yaml can be used as an example

Taking the following Pet model from the referency specification:

    Pet:
      title: a Pet
      description: A pet for sale in the pet store
      type: object
      required:
        - name
        - photoUrls
      properties:
        id:
          type: integer
          format: int64
        category:
          $ref: '#/components/schemas/Category'
        name:
          type: string
          example: doggie
          nullable: true
        photoUrls:
          type: array
          xml:
            name: photoUrl
            wrapped: true
          items:
            type: string

The Pet class would be generated like:

public class Pet {

  @JsonProperty("id")
  private Long id;

  @JsonProperty("category")
  private Category category;

  @JsonProperty("name")
  private String name;

  @JsonProperty("photoUrls")
  @Valid
  private List<String> photoUrls = new ArrayList<>();
}

Although, because only name and photoUrls are declared as required, all other properties should be wrapped in JsonNullable and name should be initialized with null as it is declared as nullable additionally.

public class Pet {

  @JsonProperty("id")
  private JsonNullable<Long> id = JsonNullable.undefined();

  @JsonProperty("category")
  private JsonNullable<Category> category = JsonNullable.undefined();

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

  @JsonProperty("photoUrls")
  @Valid
  private List<String> photoUrls = new ArrayList<>();
}
Generation Details
./bin/generate-samples.sh bin/configs/spring-openapi-nullable-required.yaml
Steps to reproduce

I already created a pull request: #14766

Related issues/PRs

Didn’t find any existing issue to this (which is quite suprising).

Suggest a fix

I already created a pull request: #14766

I am totally aware that this is a huge breaking change so I would like to discuss this issue openly. Maybe we need some kind of flag for the new behaviour. Any critics to this behaviour are welcome.

About this issue

  • Original URL
  • State: open
  • Created a year ago
  • Reactions: 4
  • Comments: 36 (22 by maintainers)

Commits related to this issue

Most upvoted comments

@daberni in general there are 4 possible scenarion for nullable + required

  1. nullable=true & required=true - means field itself is required to be present in payload, but can be set to null. No need to add JsonNullable
  2. nullable=true & required=false - means that payload for this field should match to one of the below case a) can have valid value b) can be set to null with the meaning “let’s set value (ex. in DB) explicitly to null” c) can be absent which mean “do nothing with this field”
  3. nullable=false & required=true - same as point 1, but null does not allowed. No need to use JsonNullable
  4. nullable=false & required=false - same as point 2, but b) does not applicable here since can not be set to null. No need to use JsonNullable

So based on all these cases only for point 2 we can use JsonNullabl, because that’s the only case where valid value, null & undefined(not present) can happen

@gnuemacscoin When the property is null in the source object, it means that it should be set to null. When it is not present, than it should be not modified. That is why JsonNullable is used. To provide isPresent() function and distinguish the third state.

@welshm Interesting approach, I didn’t see it this way so far but I understand the idea.

Probably this would be a somehow working approach, the problem is, that it is not implemented this way right now.

Currently, usage of JsonNullable is only determined by the definition of nullable. You can find this in the nullableDataType.mustache template. As far as I can see, there is no further differentiation.

If this would be combined with required this might be one way.

But additionally, which is quite confusing, the beanValidation.mustache depends on required instead of nullable.

So that this would work as you described, one would have to combine this together with openapiNullable too.

One problem which currently also arises, probably because of the combination of the above incomplete implementations, that Spring Model Binding Validation doesn’t work properly either. @NotNull annotations are missing on nullable=false annotations, etc.

Also after generating the code it is no longer clear which combination of flags resulted in the current code. So as a developer I would have to write different validation and mapping logic depending on required/nullable attributes, instead of coupling my mapping logic to the present types directly, which would be way more straight forward.

I see that optimizing away the one case required=false & nullable=false where theoretically JsonNullable could be determined by if the value is null or not might be an idea. But this kinda doesn’t work with real life scenarios, where only because the openapi-spec tells it this way, clients of the API wouldn’t sent null although it is defined nullable=false. So as a developer one must still be able to determine, if a client did send null for a nullable=false & required=false property to throw proper errors. This is currently not possible and the source of why we started to discuss this topic.

For better understanding, It would be more verbose to couple JsonNullable to the required attribute, and @NotNull to the nullable attribute.

Just stumbled around an additional mustache template >beanValidation where required and isNullable are also mixed up.