Skip to content

fix(json-schema): stop merging descriptions of sibling $ref properties#3048

Open
schani wants to merge 6 commits into
masterfrom
agent/fix-issue-1582
Open

fix(json-schema): stop merging descriptions of sibling $ref properties#3048
schani wants to merge 6 commits into
masterfrom
agent/fix-issue-1582

Conversation

@schani

@schani schani commented Jul 20, 2026

Copy link
Copy Markdown
Member

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.schema produced both properties with the same, concatenated description ("name description\nsurname description") instead of each keeping its own.

Root cause

name and surname are both plain string types defined via separate $defs entries with distinct descriptions. 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 (via descriptionTypeAttributeKind.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 with Internal error: Can't add different identity type attributes to an existing type on real-world schemas with more complex $ref/union structures (e.g. test/inputs/schema/vega-lite.schema).

Fix

Instead of changing type identity, JSONSchemaInput now captures each property's description from its $ref target and attaches it to the containing object's propertyDescriptions attribute — the same per-property description mechanism already used elsewhere in the renderer pipeline (see ConvenienceRenderer.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 to test/inputs/schema/, so it's automatically picked up by every schema-* fixture (JSON round-trip / compile coverage across all target languages), giving incidental broader coverage too.

Verification

  • npm run build — passes
  • npx vitest run test/unit — 164/164 passed
  • Repro re-run: name"name description", surname"surname description", no merging
  • QUICKTEST=true FIXTURE=schema-golang script/test — 68/68 passed, including vega-lite.schema (the schema that crashed under the earlier, reverted inIdentity approach)

CI will validate the remaining language fixtures.

Fixes #1582

🤖 Generated with Claude Code

schani and others added 6 commits July 20, 2026 18:57
#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>
@github-actions

Copy link
Copy Markdown

Generated-output differences

100 files differ — 73 modified, 27 new, 0 deleted
2245 changed lines — +2231 / −14

Open the generated-output report →

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Incorrect description merging

1 participant