1

I am trying to create a set of POJOs from an OpenAPI specification that is generated from a pydantic data model. The problem I'm hitting is with discriminators. I am currently using the following configuration:

                            <cleanupOutput>false</cleanupOutput>
                            <generatorName>java</generatorName>
                            <modelPackage>com.model</modelPackage>
                            <generateApis>false</generateApis>
                            <generateApiDocumentation>false</generateApiDocumentation>
                            <generateApiTests>false</generateApiTests>
                            <generateSupportingFiles>false</generateSupportingFiles>
                            <generateApiTests>false</generateApiTests>
                            <generateModels>true</generateModels>
                            <generateModelTests>false</generateModelTests>
                            <configOptions>
                                <library>resttemplate</library>
                                <useJakartaEe>true</useJakartaEe>
                                <containerDefaultToNull>true</containerDefaultToNull>
                                <generateBuilders>true</generateBuilders>
                                <serializationLibrary>jackson</serializationLibrary>
                                <performBeanValidation>false</performBeanValidation>
                                <useBeanValidation>true</useBeanValidation>
                                <generateClientAsBean>false</generateClientAsBean>
                                <sortModelPropertiesByRequiredFlag>true</sortModelPropertiesByRequiredFlag>
                                <useOneOfInterfaces>true</useOneOfInterfaces>
                                <legacyDiscriminatorBehavior>false</legacyDiscriminatorBehavior>
                            </configOptions>
                        </configuration>

My Pydantic model is

from enum import Enum
from typing import Annotated, Literal, Union

from pydantic import BaseModel, Field
from typing_extensions import TypeAliasType


class PetType(Enum):
    CAT = "cat"
    DOG = "dog"


class Cat(BaseModel):
    pet_type: Literal[PetType.CAT]
    meows: int


class Dog(BaseModel):
    pet_type: Literal[PetType.DOG]
    barks: float


Pet = TypeAliasType('PetTypes', Annotated[Cat | Dog, Field(discriminator="pet_type")])


class Model(BaseModel):
    pet: list[Pet] = Field(
        ...,
        description="My Pets.",
    )

This generates an OpenAPI spec (using from openapi_pydantic.util import construct_open_api_with_schema_class,with a made up endpoint) of:

openapi: 3.1.0
info:
  title: Model
  version: 0.1.0
paths:
  /model:
    post:
      summary: Create Model
      requestBody:
        content:
          application/json: {}
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Model'
components:
  schemas:
    Cat:
      properties:
        pet_type:
          type: string
          const: cat
          title: Pet Type
        meows:
          type: integer
          title: Meows
      type: object
      required:
      - pet_type
      - meows
      title: Cat
    Dog:
      properties:
        pet_type:
          type: string
          const: dog
          title: Pet Type
        barks:
          type: number
          title: Barks
      type: object
      required:
      - pet_type
      - barks
      title: Dog
    Model:
      properties:
        pet:
          items:
            $ref: '#/components/schemas/PetTypes'
          type: array
          title: Pet
          description: My Pets.
      type: object
      required:
      - pet
      title: Model
    PetTypes:
      oneOf:
      - $ref: '#/components/schemas/Cat'
      - $ref: '#/components/schemas/Dog'
      discriminator:
        propertyName: pet_type
        mapping:
          cat: '#/components/schemas/Cat'
          dog: '#/components/schemas/Dog'

Running mvn package will generate me an interface for discriminated values, and then concrete classes that extend that interface. But the getType() is incorrect. The interface has

public interface PetTypes {
    public String getPetType();
}

But the implementations (Dog.java, Cat.java)have

  public PetTypeEnum getPetType() {
    return petType;
  }

If I can get the generated OpenAPI schema to look like this, it generates the correct getPetType() methods, but I haven't been able to figure out how to get to here from the pydantic model.

openapi: 3.1.0
info:
  title: Model
  version: 0.1.0
paths:
  /model:
    post:
      summary: Create Model
      requestBody:
        content:
          application/json: {}
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Model'
components:
  schemas:
    PetTypeEnum:
      type: string
      enum:
        - cat
        - dog
    Cat:
      properties:
        pet_type:
          $ref: '#/components/schemas/PetTypeEnum'
        meows:
          type: integer
          title: Meows
      type: object
      required:
      - pet_type
      - meows
      title: Cat
    Dog:
      properties:
        pet_type:
          $ref: '#/components/schemas/PetTypeEnum'
        barks:
          type: number
          title: Barks
      type: object
      required:
      - pet_type
      - barks
      title: Dog
    Model:
      properties:
        pet:
          items:
            $ref: '#/components/schemas/PetTypes'
          type: array
          title: Pet
          description: My Pets.
      type: object
      required:
      - pet
      title: Model
    PetTypes:
      oneOf:
      - $ref: '#/components/schemas/Cat'
      - $ref: '#/components/schemas/Dog'
      discriminator:
        propertyName: pet_type
        mapping:
          cat: '#/components/schemas/Cat'
          dog: '#/components/schemas/Dog'

I've found https://github.com/OpenAPITools/openapi-generator/issues/13484, https://github.com/OpenAPITools/openapi-generator/issues/12412 & https://github.com/pydantic/pydantic/issues/4337 but none are exactly referencing the issue I'm hitting. The last one seems to have been a fix that supports the references in the discriminator property. Turning off useOneOfInterfaces generates public class PetTypes with the superset of all the properties, but only Dog in the PetTypesEnum . Additionally, it seems to try to deserialize into the Cat.java and Dog.java classes, which then can't cast into the list of pets.

Any help would be appreciated.

Versions:

pydantic: 2.12.3
openapi-pydantic: 0.5.1
Open API spec: 3.1.0

1 Answer 1

1

After construct_open_api_with_schema_class(...) add an enum schema

openapi.components.schemas["PetTypeEnum"] = {
    "type": "string",
    "enum": ["cat", "dog"],
}

or rewrite Cat and Dog

for name in ["Cat", "Dog"]:
    pet_schema = openapi.components.schemas[name].properties["pet_type"]
    # replace the generated literal schema with a ref
    openapi.components.schemas[name].properties["pet_type"] = {
        "$ref": "#/components/schemas/PetTypeEnum"
    }
    # optionally drop 'const' if it was present

or leave the discriminator mapping as-is.

  • The resulting YAML will match your “working” example and openapi-generator will generate PetTypeEnum getPetType() in the interface as well.

  • Accept String in the interface

  • Model the discriminator purely as string literals on the Python side (as you already do) and live with String getPetType() in the interface. You can then add your own Java enum and convert in your application code.

  • Avoid useOneOfInterfaces

  • Turn off useOneOfInterfaces so you get a concrete PetTypes class instead of an interface. That avoids the override-type mismatch, at the cost of a less “polymorphic” model structure.

Sign up to request clarification or add additional context in comments.

1 Comment

I can't leave the discriminator mapping as it, it generates java code that doesn't compile. I also can't just handle it application code without extra coding because I'm just using Jackson's ObjectMapper to handle the Deserialization and that fails. Based on the first two options, it sounds like post processing is the only way here? There's no way to generate the spec in the preferred manner? I ask because the above was a simplified example, I have more than one enum here. Ideally I would like a solution that generates the proper values without post processing

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.