Skip to content

Field order is wrong when non-required property is listed before required in schema #3056

@k-jell-encentive

Description

@k-jell-encentive

Describe the bug
If a non-required property in the schema is listed before any required property, the field order is wrong when using pydantic dataclasses where fields where a field with default value can't appear before fields without. It seems like the sorting doesn't work correctly.

To Reproduce
Example schema:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "UserProfile",
  "type": "object",
  "required": ["name", "id", "email"],
  "properties": {
    "age": {
      "type": "integer",
      "minimum": 0
    },
    "id": {
      "type": "string",
      "description": "Unique identifier for the user"
    },
    "name": {
      "type": "string",
      "minLength": 1
    },
    "email": {
      "type": "string",
      "format": "email"
    },
    "isActive": {
      "type": "boolean",
      "default": true
    },
    "role": {
      "type": "string",
      "enum": ["admin", "editor", "viewer"]
    },
    "tags": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "address": {
      "type": "object",
      "required": ["city"],
      "properties": {
        "street": {
          "type": "string"
        },
        "city": {
          "type": "string"
        },
        "postalCode": {
          "type": "string",
          "pattern": "^[0-9]{5}$"
        }
      }
    }
  },
  "additionalProperties": false
}

Used commandline:

$ datamodel-codegen   --input ./test_schema.json   --input-file-type jsonschema   --output ./model.py   --snake-case-field --output-model-type pydantic_v2.dataclass --formatters ruff-format ruff-check isort --no-use-type-checking-imports --use-annotated --use-union-operator --use-standard-collections --use-default

Expected behavior
I get this:

@dataclass(config=ConfigDict(extra="forbid"))
class UserProfile:
    email: EmailStr
    age: Annotated[int | None, Field(ge=0)] = None
    id: Annotated[str, Field(description="Unique identifier for the user")]
    name: Annotated[str, Field(min_length=1)]
    is_active: Annotated[bool | None, Field(alias="isActive")] = True
    role: Role | None = None
    tags: list[str] | None = None
    address: Address | None = None

but I would expect this:

@dataclass(config=ConfigDict(extra="forbid"))
class UserProfile:
    email: EmailStr
    id: Annotated[str, Field(description="Unique identifier for the user")]
    name: Annotated[str, Field(min_length=1)]
    is_active: Annotated[bool | None, Field(alias="isActive")] = True
    age: Annotated[int | None, Field(ge=0)] = None
    role: Role | None = None
    tags: list[str] | None = None
    address: Address | None = None

Version:

  • OS: linux
  • Python version: tested with 3.11 and 3.13
  • datamodel-code-generator version: 0.55.0
    Additional context
    Add any other context about the problem here.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions