OpenAPITools / OpenAPITools/openapi-generator

[BUG] [java] nullable oneOf deserialization incorrect for isNullable

Open
#20,399 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Issue: Bug
Dominant language
Java
Stars
26.8k
Forks
7.7k
PR merge metrics
PR metrics pending

Description

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

When using a nullable oneOf property, deserialization does not preserve the isNullable flag.

openapi-generator version

All versions since 7.5.0.
7.4.0 works correctly.

OpenAPI declaration file content or url
openapi: 3.0.3
info:
  title: API
  description: Hello
  version: v1
servers:
  - url: http://localhost:8080/
    description: server url
tags:
  - name: "Hello"
    description: "The Hello resource"
paths:
  /hello:
    post:
      tags:
        - Hello
        - FIDO
      operationId: hello
      summary: Returns a hello message.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/HelloRequest'
      responses:
        '200': # status code
          description: Succeeded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HelloResponse'

components:
  schemas:
    HelloRequest:
      type: object
      properties:
        message:
          type: string
          minLength: 1
          example: "friends"
        optionalField:
          type: integer
          nullable: true
        polymorphicProperties:
          type: array
          items:
            $ref: '#/components/schemas/ParentProperty'
        propertyOption:
          $ref: '#/components/schemas/PropertyOption'
      required:
      - message

    HelloResponse:
      type: object
      properties:
        message:
          type: string
          example: "Hello friends"

    ParentProperty:
      type: object
      discriminator:
        propertyName: "propertyType"
      properties:
        parentValue:
          type: string

    ChildProperty1:
      allOf:
        - $ref: '#/components/schemas/ParentProperty'
        - type: object
          properties:
            stringValue:
              type: string
            propertyType:
              type: string
              default: "ChildProperty1"
          required:
            - stringValue

    ChildProperty2:
      type: object
      allOf:
        - $ref: '#/components/schemas/ParentProperty'
        - type: object
          properties:
            intValue:
              type: integer
            propertyType:
              type: string
              default: "ChildProperty2"
          required:
            - intValue

    PropertyOption:
      nullable: true
      oneOf:
        - $ref: '#/components/schemas/PropertyOption1'
        - $ref: '#/components/schemas/PropertyOption2'

    PropertyOption1:
      type: object
      properties:
        stringValue:
          type: string
      required:
        - stringValue

    PropertyOption2:
      type: object
      properties:
        intValue:
          type: integer
      required:
        - intValue
Generation Details

I'm Using gradle plugin:

openApiGenerate {
    generatorName = 'java'
    additionalProperties = [
            sourceFolder : "",
            "annotationLibrary": "none",
            "library" : "jersey2",
            "performBeanValidation" : "true",
            "useBeanValidation" : "true",
            "useRuntimeException" : "true",
            "hideGenerationTimestamp" : "true",
            "useJakartaEe": "true"
    ]
}
Steps to reproduce

Generate Java models and perform a JSON round-trip of the DTO:

@Test
void request() throws JsonProcessingException {
    HelloRequest initial = new HelloRequest()
        .message("message")
        .optionalField(15)
        .polymorphicProperties(List.of(
                new ChildProperty1()
                        .stringValue("stringValue"),
                new ChildProperty2()
                        .intValue(10)))
        .propertyOption(
                new PropertyOption(
                        new PropertyOption2()
                                .intValue(15)));

    String json = mapper.writeValueAsString(initial);

    HelloRequest fromJson = mapper.readValue(json, HelloRequest.class);

    assertThat(fromJson).isEqualTo(initial); // Fails because propertyOption.isNullable is true in fromJson
}
Related issues/PRs
Suggest a fix

The deserialization logic constructs an empty PropertyOption (using the default constructor which initializes isNullable to true) and then calls setActualInstance. So options I can see is either change isNullable in setActualnstance or change deserialization logic to use the constructor that takes the instance as an argument.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the generated Java PropertyOption model and its deserialization path, especially the default constructor and setActualInstance mentioned in the report. Reproduce the Gradle-generated model round trip with the provided request test, then verify that deserialization preserves the original isNullable value and the equality assertion passes.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, openapi
Domain
api, backend, testing-qa
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.