fix(core): preserve outer oneOf title when flattened with null - #2980
Open
schani wants to merge 5 commits into
Open
fix(core): preserve outer oneOf title when flattened with null#2980schani wants to merge 5 commits into
schani wants to merge 5 commits into
Conversation
A `oneOf` union of single-value string enums that carries its own
`title` (e.g. a Rust schemars "enum of enums") lost that title when
wrapped in an `anyOf` with `{ "type": "null" }`. FlattenUnions/
UnionBuilder collapsed the nested homogeneous union into the single
non-null member type, but the outer union's title attribute stayed on
the discarded nullable union, so naming fell back to the first
variant's title (e.g. `Turtle`) instead of the intended union title
(e.g. `DataFormat`).
Fixed by attributing a nested homogeneous union's own title/attributes
to the type it collapses into whenever it sits alongside null in the
enclosing union, in UnionBuilder.ts's attributesForTypes.
Added test/inputs/schema/enum-of-enums-null.schema (+ .1.json/.2.json
samples) covering the exact reported shape as an end-to-end schema
fixture, plus test/unit/enum-of-enums-null-name.test.ts asserting the
generated TypeScript names the type DataFormat rather than Turtle.
Fixes #2410
Co-Authored-By: gpt-5.6-sol via pi <noreply@openai.com>
Co-Authored-By: Claude <noreply@anthropic.com>
The enum-of-enums-null.schema fail sample relies on rejecting an invalid enum value, which languages that do not validate enum values (cjson, crystal, swift, haskell, typescript-zod, typescript-effect-schema) do not enforce. Add the schema to skipsEnumValueValidation so those languages skip it, matching the existing enum.schema pattern. Validating languages still run both positive and negative cases. Co-Authored-By: Claude <noreply@anthropic.com>
# Conflicts: # test/languages.ts
# Conflicts: # test/languages.ts
Generated-output differences31 files differ — 0 modified, 31 new, 0 deleted |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bug
A JSON Schema
oneOfunion of single-value string enums that carries itsown
title(e.g. the "enum of enums" shape Rust'sschemarsemits) losesthat title when the union is made nullable via an enclosing
anyOfwith{ "type": "null" }. quicktype instead names the generated type after thefirst variant's title.
Repro:
{ "properties": { "format": { "anyOf": [{ "$ref": "#/definitions/DataFormat" }, { "type": "null" }] } }, "definitions": { "DataFormat": { "title": "DataFormat", "oneOf": [ { "title": "Turtle", "type": "string", "enum": ["turtle"] }, { "title": "RDF XML", "type": "string", "enum": ["rdf_xml"] }, { "title": "N-Triples", "type": "string", "enum": ["n_triples"] }, { "title": "N-Quads", "type": "string", "enum": ["n_quads"] } ] } } }Before:
After:
Root cause
When the inline
DataFormatunion is flattened alongsidenullfrom theenclosing
anyOf,UnionBuilder'sattributesForTypescollapses thenested homogeneous union into its single non-null member type, but the
titleattribute carried by that nested union stayed attached to thediscarded nullable-union node instead of the collapsed-into type. Naming
then fell back to the first variant's own title. This affected every output
language (verified TypeScript and Go), since the defect is in
quicktype-core, not a renderer.Fix
packages/quicktype-core/src/UnionBuilder.ts: when computing attributes fortypes in
attributesForTypes, detect nested unions that are homogeneous(all members share one kind) and sit alongside only
nullin an enclosingunion — i.e. unions that get fully absorbed into a single non-null type
during unification — and fold their own attributes (title, description,
etc.) onto that resulting type, at increased distance so more specific
names still win where applicable.
Tests
test/inputs/schema/enum-of-enums-null.schema(+.1.json/.2.jsonsamples): a new end-to-end JSON Schema fixture reproducing the exact
reported shape, picked up automatically by the schema fixture harness for
every target language.
test/unit/enum-of-enums-null-name.test.ts: a focused unit test assertingthe generated TypeScript names the type
DataFormatand never falls backto
Turtle, since fixture round-tripping alone can't assert on the chosenidentifier.
Verification
npm run buildpasses.npm run test:unit— 164/164 passing.--lang tsand--lang go: both now emitDataFormatinstead ofTurtle.QUICKTEST=true FIXTURE=schema-golang script/test— 68/68 passing,including the new
enum-of-enums-null.schemafixture.QUICKTEST=true FIXTURE=schema-typescript script/test— passing except asingle pre-existing, unrelated failure on
vega-lite.schema(reproducesidentically on master without this change; a
noUncheckedIndexedAccess-stylestrict-mode TS2411 clash unrelated to unions/enums). The new
enum-of-enums-null.schemafixture passes.Fixes #2410
🤖 Generated with Claude Code