[DNM] Handle allOf and oneOf in json_schema_to_type#3840
[DNM] Handle allOf and oneOf in json_schema_to_type#3840
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 62c3f79d06
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| merged: dict[str, Any] = {} | ||
| merged_properties: dict[str, Any] = {} | ||
| merged_required: list[str] = [] |
There was a problem hiding this comment.
Preserve sibling fields when flattening allOf
The allOf merge path initializes merged/merged_properties from empty dicts and only copies fields from each allOf child, so any sibling properties/required already present on the current schema are dropped. In nested schemas that combine local fields with allOf, this now silently stops validating previously-required local fields (and omits them from the parsed object), which is a regression in validation behavior for composed objects.
Useful? React with 👍 / 👎.
| for subschema in schema["oneOf"]: | ||
| types.append(_schema_to_type(subschema, schemas)) |
There was a problem hiding this comment.
Keep map-like branches intact in oneOf unions
The oneOf loop always calls _schema_to_type(subschema, schemas) directly, unlike anyOf which special-cases object schemas with additionalProperties to produce dict[...]. As a result, a branch like {"type":"object","additionalProperties":{"type":"string"}} becomes an empty dataclass in oneOf, so arbitrary dict inputs validate as Root() and key/value data is discarded instead of being type-checked as a mapping.
Useful? React with 👍 / 👎.
62c3f79 to
4112e18
Compare
Previously these compositions silently resolved to Any, disabling all validation for schemas that use them. allOf is now merged into a single object schema; oneOf creates a Union type. 🤖 Generated with Claude Code Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
4112e18 to
39f8815
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 39f88156e5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if isinstance(sub, bool): | ||
| return |
There was a problem hiding this comment.
Treat
false branches in allOf as unsatisfiable
Returning early for boolean sub-schemas silently ignores false inside allOf, but JSON Schema semantics require allOf to fail if any member is false. With the current logic, a schema like {"allOf": [false, {"type":"object", ...}]} still validates inputs against the object branch, so invalid payloads are accepted instead of being rejected.
Useful? React with 👍 / 👎.
| if "$ref" in sub: | ||
| sub = dict(_resolve_ref(sub["$ref"], schemas)) |
There was a problem hiding this comment.
Avoid dict-casting boolean refs in allOf flattening
When an allOf member is a $ref to a boolean schema (valid in draft-06+), this code does dict(_resolve_ref(...)), which raises TypeError for true/false targets. That makes json_schema_to_type crash on valid schemas such as {"$defs": {"Never": false}, "allOf": [{"$ref": "#/$defs/Never"}]} instead of producing an unsatisfiable type.
Useful? React with 👍 / 👎.
| merged_properties.update(schema.get("properties", {})) | ||
| merged_required.extend(schema.get("required", [])) |
There was a problem hiding this comment.
Preserve top-level additionalProperties in allOf merge
The allOf merge only copies sibling properties/required from the parent schema, so parent-level additionalProperties is dropped. For schemas that combine allOf with additionalProperties: true, this changes behavior from the normal object path (which builds a BaseModel with extras allowed) to a dataclass path that drops unknown keys, causing silent data loss for extra fields.
Useful? React with 👍 / 👎.
Summary
json_schema_to_type()handlesanyOfbut silently returnsAnyforallOfandoneOfschemas, disabling all validation. This is the most common schema composition pattern in OpenAPI 3.x specs.allOf(schema intersection) sub-schemas are now merged into a single object type. Properties and required fields from all sub-schemas are combined.$refsub-schemas are resolved before merging. NestedallOf(including$ref→allOf) is flattened recursively.oneOf(exactly-one-of) is now treated likeanyOf— creates a Union type. Pydantic's "first match" semantics are a reasonable approximation.Known limitations
Property constraint merging: if two allOf sub-schemas define the same property with different constraints, the second sub-schema's definition wins (last-writer from
dict.update). Merging per-property constraints (e.g., combiningminLengthfrom one withmaxLengthfrom another) is not implemented. This is rare in practice — the standard pattern uses allOf to combine different properties.Non-object allOf:
allOfwith scalar sub-schemas that add constraints (e.g.,[{type: string}, {minLength: 3}]) doesn't merge the constraints. Object composition (the dominant real-world use case) works correctly.Test coverage
7 test cases: basic merge, required preservation,
$refresolution, nestedallOf,$ref→allOf, object union, scalar union. 52 lines of source + 169 lines of tests.Closes #3839
🤖 Generated with Claude Code