Skip to content

fix(json-schema): stop forType custom attributes leaking across interned primitive types#3053

Open
schani wants to merge 4 commits into
masterfrom
agent/fix-issue-1268
Open

fix(json-schema): stop forType custom attributes leaking across interned primitive types#3053
schani wants to merge 4 commits into
masterfrom
agent/fix-issue-1268

Conversation

@schani

@schani schani commented Jul 20, 2026

Copy link
Copy Markdown
Member

Bug

CustomAttributeProducers (as used by the quicktype-custom-*-example pattern
referenced in the issue) can return { forType: someTypeAttributes } from a
JSONSchemaAttributeProducer to attach custom type attributes to the type
produced for a specific schema node. For plain, unrestricted primitive types
(most commonly string), this leaked onto every occurrence of that
primitive kind in the schema, not just the one the producer targeted.

Repro from the issue: a schema marks emailAddress and phoneNumber (both
plain strings) as deprecated: true via a custom producer. An unrelated
plain-string property, externalId, ends up marked deprecated too, even
though nothing in the schema marks it as such.

Root cause

TypeBuilder.getOrAddType interns types by a TypeIdentity computed from the
type kind plus only the identity attributes (TypeAttributeKind.inIdentity === true). Custom attribute kinds default inIdentity and
requiresUniqueIdentity to false. Since most plain string properties in a
schema resolve to the same identity (kind "string" + StringTypes.unrestricted),
getOrAddType finds and reuses the same interned PrimitiveType("string")
node for all of them. When a forType attribute is attached to one
occurrence, it's actually merged onto that single shared node, so it "leaks"
to every other plain string property in the schema.

(Built-in producers avoid this today: descriptionAttributeProducer routes
per-property descriptions through forObject + a property-keyed attribute
map specifically to avoid this class of leak — but that workaround isn't
available to library consumers using the public forType API.)

Fix

In packages/quicktype-core/src/input/JSONSchemaInput.ts, wrap any non-empty
forType attributes with an internal forTypeIdentityTypeAttributeKind
marker attribute (requiresUniqueIdentity() => true) before combining them
into a type's attributes. This forces the type-graph builder to skip
interning for any type that carries occurrence-specific forType attributes,
so it always gets its own graph node instead of being silently shared with
unrelated occurrences of the same primitive kind. The marker attribute itself
is internal-only (default combine/reconstitute behavior, no
addToSchema), so it's invisible to renderers and generated output.

Test coverage

Added test/unit/json-schema-custom-attributes.test.ts, a focused unit test
using JSONSchemaInput with a custom deprecated attribute producer (mirroring
the pattern from the linked example repos) that:

  • fails on unfixed code (confirmed externalId incorrectly ends up
    deprecated)
  • passes after the fix (externalId has no deprecated key; emailAddress
    and phoneNumber correctly do)

This is unit-test-only because CustomAttributeProducer/forType is a
quicktype-core library API feature — it requires registering a custom
TypeAttributeKind and JSONSchemaAttributeProducer programmatically, which
isn't reachable through the quicktype CLI or plain JSON/JSON-Schema fixture
input files. Per repo conventions this is exactly the kind of API-level
behavior test/unit/ is for.

Verification

  • npm run build: passes
  • npm run test:unit: 177/177 tests pass (including the new regression test)
  • QUICKTEST=true FIXTURE=schema-typescript script/test: 71/71 pass locally,
    including the description.schema, accessors.schema,
    comment-injection*.schema, and enum*.schema fixtures that already
    exercise the built-in forType-based description/accessor/enum-values
    producers — no regression from widening forType handling.
  • Manually re-ran the exact repro from the issue against the built dist
    before and after the fix: externalId no longer inherits deprecated from
    unrelated properties.
  • CI will additionally validate across the full fixture matrix.

Fixes #1268

🤖 Generated with Claude Code

schani and others added 2 commits July 20, 2026 19:14
@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown

No generated-output differences

✅ This PR does not change generated outputs.

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.

CustomAttributeProducer and custom rendering doesnt work

1 participant