Skip to content

field: Schema | None = None should generate anyOf where null is the last item #1027

@sobolevn

Description

@sobolevn

Hi 👋

I am using msgspec to generate JsonSchema for my schemas in wemake-services/django-modern-rest#990

But, there's a big difference with how msgspec and pydantic generates schema for this type:

class _TailsSchema(pydantic.BaseModel):
    id: int
    name: str

class _UserCreateModel(pydantic.BaseModel):
    tails: _TailsSchema | None = None

vs

class _TailsSchema(msgspec.Struct):
    id: int
    name: str

class _UserCreateModel(msgspec.Struct):
    tails: _TailsSchema | None = None

What schema is generated

pydantic:

"_UserCreateModel": {
  "properties": {
    "tails": {
      "anyOf": [
        {
          "$ref": "#/components/schemas/_TailsSchema"
        },
        {
          "type": "null"
        }
      ]
    },
    // ...
}

msgspec:

"_UserCreateModel": {
  "properties": {
    "tails": {
      "anyOf": [
        {
          "type": "null"
        },
        {
          "$ref": "#/components/schemas/_TailsSchema"
        }
      ]
    },
    // ...
}

The final result from msgspec makes Swagger to render

{
  "email": "string",
  "tail": null,
  "tail_optional": null
}

as the example value, which is correct, but less ideal than the pydantic's version:

{
  "email": "string",
  "tail": {
    "id": 0,
    "name": "string"
  },
  "tail_optional": {
    "id": 0,
    "name": "string"
  }
}

Looks like that Swagger just takes the first value from anyOf, it would be reasonable to change the order. It would also be more semantically similar to how Schema | None is used in Python, not None | Schema

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions