Skip to content

Top-level oneOf dropped when sibling allOf present at same level (Draft-07, Pydantic v2) #3158

Description

@bokelley

Version observed: 0.56.1
Python: 3.14.4
Pydantic target: v2
Severity: generated types are silently incomplete — no error or warning

When a JSON Schema (Draft-07) object has both a top-level allOf and a
top-level oneOf, codegen 0.56.1 emits a class that inherits from the
allOf-derived base but silently drops every oneOf branch. No error,
no warning — just an incomplete model. The expected output is either a
discriminated-union root model (each variant inheriting the allOf base) or
at minimum a clear failure that signals codegen can't represent the shape.

Minimal reproduction

base.json (the allOf target):

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "Envelope",
  "type": "object",
  "properties": {
    "version": { "type": "string" }
  }
}

response.json (the schema codegen mis-handles):

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "Response",
  "type": "object",
  "allOf": [
    { "$ref": "base.json" }
  ],
  "oneOf": [
    {
      "title": "Success",
      "properties": {
        "status": { "type": "string", "const": "success" },
        "data": { "type": "string" }
      },
      "required": ["status", "data"]
    },
    {
      "title": "Error",
      "properties": {
        "status": { "type": "string", "const": "error" },
        "message": { "type": "string" }
      },
      "required": ["status", "message"]
    }
  ],
  "properties": {}
}

Invocation:

datamodel-codegen \
  --input ./schemas \
  --input-file-type jsonschema \
  --output-model-type pydantic_v2.BaseModel \
  --output ./generated

Actual output (response.py)

from .base import Envelope


class Response(Envelope):
    pass

The four-branch discriminated union is gone.

Expected output

Something equivalent to:

from pydantic import RootModel
from .base import Envelope


class ResponseSuccess(Envelope):
    status: Literal["success"]
    data: str


class ResponseError(Envelope):
    status: Literal["error"]
    message: str


class Response(RootModel[ResponseSuccess | ResponseError]):
    root: Annotated[ResponseSuccess | ResponseError, Field(discriminator="status")]

Real-world impact

This pattern shows up in 31 response schemas in the AdCP protocol
(adcontextprotocol/adcp 3.1.0-beta.1) — every response that wraps a
version envelope and returns a discriminated success/error union.
Downstream consumers see the schema-declared variant shapes go missing
from generated types, with no diagnostic — the failure surfaces much later
as AttributeError / KeyError at runtime when callers reach for fields
the codegen dropped.

The workaround we ended up applying in a prep step rewrites the schema
before passing it to codegen: merge the allOf branch properties into
each oneOf branch, then delete the top-level allOf. That produces
correct output, but only because we have a controlled prep pipeline —
SDK adopters who just run datamodel-codegen against vendor schemas
have no equivalent escape hatch.

Suggested fix paths

Two options that would both be acceptable, in priority order:

  1. Generate the discriminated union, treating allOf as additional base
    fields per branch.
    Each oneOf branch class inherits the allOf-derived
    base; the top-level type is a RootModel union. Matches author intent
    and standard JSON Schema semantics.
  2. Emit a hard error. If (1) is implementation-prohibitive, at minimum
    refuse to generate and tell the user the shape isn't supported. The
    silent drop is the worst failure mode here.

Environment

  • Python 3.14.4 (also reproduces on 3.10–3.13)
  • datamodel-code-generator==0.56.1
  • pydantic 2.12.x
  • Schemas: Draft-07
  • macOS 14 / Linux x86_64 — same behavior on both

Happy to provide the full 31-schema-tree reproduction if useful.

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