fix(json-schema): stop merging descriptions of sibling $ref properties#3048
Open
schani wants to merge 6 commits into
Open
fix(json-schema): stop merging descriptions of sibling $ref properties#3048schani wants to merge 6 commits into
schani wants to merge 6 commits into
Conversation
#1582) When two sibling properties referenced distinct $defs entries that were structurally identical primitives (e.g. two plain strings with different descriptions), type-graph unification collapsed them onto one shared type, and the descriptions were concatenated onto both properties. Fix attaches each property's description from its $ref target directly to the containing object's propertyDescriptions attribute (the existing per-property description mechanism already used elsewhere in the renderer pipeline), instead of relying on the shared underlying type's description. The JSON Schema renderer now prefers this property-specific description over the type's own description when present. Added a regression unit test (fixtures can't express this: it's about metadata in the generated schema output, not runtime-checkable JSON round-tripping) plus a schema/JSON fixture pair (test/inputs/schema/description-ref.schema) that also exercises the fix through the standard schema-* fixture suite. Verified locally: npm run build, full unit suite (164/164), QUICKTEST=true FIXTURE=schema-golang script/test (68/68, including the previously-crashing vega-lite.schema), and the original repro now emits distinct descriptions for each property. Fixes #1582 Co-Authored-By: gpt-5.6-sol via pi <noreply@openai.com>
Co-Authored-By: Claude <noreply@anthropic.com>
…ts (#3048) The kotlin/kotlin-jackson fixture job coerces the JSON number in description-ref.1.fail.union.json into the string-typed property instead of rejecting it, so the expected-failure sample never actually fails. Swap it for a missing-required-property sample (the no-defaults feature), which is already proven to fail reliably across all Kotlin variants via required.schema. Co-Authored-By: gpt-5.6-sol via pi <noreply@openai.com>
The description-ref.1.fail.no-defaults.json negative case is a missing-required-property test, identical in nature to required.schema's. Languages that already skip required.schema because their generated code (or test driver) cannot turn an absent required property into a nonzero exit — cjson, swift, haskell, typescript-zod, typescript-effect-schema, and elixir — round-trip the sample instead of failing, breaking the fixture. Skip description-ref.schema for exactly that set, mirroring required.schema. The schema still runs both its positive and negative cases in csharp, python, rust, cplusplus, kotlin, and the other languages that enforce required properties. Co-Authored-By: Claude <noreply@anthropic.com>
Generated-output differences100 files differ — 73 modified, 27 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
With a JSON Schema like:
{ "$defs": { "name": { "type": "string", "description": "name description" }, "surname": { "type": "string", "description": "surname description" } }, "type": "object", "properties": { "name": { "$ref": "#/$defs/name" }, "surname": { "$ref": "#/$defs/surname" } } }running
node dist/index.js --src-lang schema --lang schema input.schemaproduced both properties with the same, concatenated description ("name description\nsurname description") instead of each keeping its own.Root cause
nameandsurnameare both plainstringtypes defined via separate$defsentries with distinctdescriptions. Because the two types are structurally identical (both just "string"), quicktype's type-graph unification collapsed them onto a single shared type, and each$ref's description got combined (viadescriptionTypeAttributeKind.combine, a set union) onto that one shared type's attributes. Since both properties then pointed at that single unified type, both properties reported the same combined description.An earlier attempt at this fix made descriptions part of type identity (
inIdentity), which prevented the collapse but broke type-graph reconstitution more broadly — it crashed withInternal error: Can't add different identity type attributes to an existing typeon real-world schemas with more complex$ref/union structures (e.g.test/inputs/schema/vega-lite.schema).Fix
Instead of changing type identity,
JSONSchemaInputnow captures each property's description from its$reftarget and attaches it to the containing object'spropertyDescriptionsattribute — the same per-property description mechanism already used elsewhere in the renderer pipeline (seeConvenienceRenderer.descriptionForClassProperty). The JSON Schema renderer now prefers this property-specific description over the shared type's own description when one is present. This keeps type unification behavior unchanged and only affects how per-property descriptions are attributed.Test coverage
test/unit/schema-description-ref.test.ts: regression unit test asserting each property keeps its own description (fails on unfixed code, passes after the fix). A unit test is used because this is about metadata in the generated schema output (descriptions/doc-comments), which the standard fixture round-trip (JSON data in → JSON data out) cannot express — matching this repo's documented convention that unit tests are for what fixtures can't cover.test/inputs/schema/description-ref.schema+.1.json: added totest/inputs/schema/, so it's automatically picked up by everyschema-*fixture (JSON round-trip / compile coverage across all target languages), giving incidental broader coverage too.Verification
npm run build— passesnpx vitest run test/unit— 164/164 passedname→"name description",surname→"surname description", no mergingQUICKTEST=true FIXTURE=schema-golang script/test— 68/68 passed, includingvega-lite.schema(the schema that crashed under the earlier, revertedinIdentityapproach)CI will validate the remaining language fixtures.
Fixes #1582
🤖 Generated with Claude Code