Skip to content

fix(schema): honor single-entry patternProperties as additionalProperties - #3008

Open
schani wants to merge 7 commits into
masterfrom
agent/fix-issue-1643
Open

fix(schema): honor single-entry patternProperties as additionalProperties#3008
schani wants to merge 7 commits into
masterfrom
agent/fix-issue-1643

Conversation

@schani

@schani schani commented Jul 20, 2026

Copy link
Copy Markdown
Member

Bug

Reported in #1643: a JSON Schema object whose value type is described with
patternProperties (rather than additionalProperties) loses all type
information when the pattern isn't the literal string .*. For example:

{
  "type": "object",
  "properties": {
    "alternators": {
      "type": "object",
      "patternProperties": {
        "(^[A-Za-z0-9]+$)": {
          "type": "object",
          "properties": {
            "voltage": { "type": "number" },
            "name": { "type": "string" }
          }
        }
      }
    }
  }
}

generated (Swift, but reproducible in every language):

let alternators: [String: JSONAny]?

with a warning that quicktype couldn't infer the type, even though the
pattern's schema fully describes the map's value type.

Root cause

packages/quicktype-core/src/input/JSONSchemaInput.ts had a narrow hack
(originally added for #976) that only treats patternProperties as a stand-in
for additionalProperties when the single pattern key is exactly ".*".
Any other pattern — including ones that are effectively "match anything" like
(^[A-Za-z0-9]+$) — was ignored entirely, so the property fell back to
untyped any.

Fix

Generalize the existing heuristic: when additionalProperties is not
explicitly set and patternProperties has exactly one entry, use that
entry's schema as the effective additionalProperties, regardless of what
the pattern text is. Schemas with multiple patternProperties entries are
left unchanged (still unsupported — out of scope here). The .* case
continues to work exactly as before.

let additionalProperties = schema.additionalProperties;
// Treat a single pattern property as additional properties since
// per-pattern typed properties aren't supported.
if (
    additionalProperties === undefined &&
    typeof schema.patternProperties === "object" &&
    schema.patternProperties !== null
) {
    const patterns = Object.getOwnPropertyNames(schema.patternProperties);
    if (patterns.length === 1) {
        additionalProperties = schema.patternProperties[patterns[0]];
    }
}

Test coverage

  • test/inputs/schema/pattern-properties.schema + .1.json: new JSON Schema
    fixture reproducing the reported shape (a patternProperties entry with a
    non-.* pattern describing a map of typed sub-objects). This is
    auto-discovered by the schema-* fixtures in test/fixtures.ts.
  • test/unit/pattern-properties-schema.test.ts: focused unit test asserting
    the generated Swift output contains a typed dictionary value
    ([String: Alternator]? with typed name/voltage fields) and no longer
    contains JSONAny.
  • The pre-existing .*-pattern fixture
    (test/inputs/schema/go-schema-pattern-properties.schema) still passes,
    confirming no regression to that case.

Verification

  • npm run build passes.
  • npx vitest run test/unit/pattern-properties-schema.test.ts passes.
  • QUICKTEST=true FIXTURE=schema-golang script/test — all 68 schema fixture
    tests pass, including the new pattern-properties.schema and the existing
    go-schema-pattern-properties.schema (.* case).
  • Re-ran the original repro directly (node dist/index.js -l swift -s schema --src schema.json): output now shows let alternators: [String: Alternator]? with a generated Alternator struct containing typed
    name/voltage fields, and the "cannot infer this type" warning is gone.
  • CI will additionally validate the fixture across all other supported
    target languages.

Fixes #1643

🤖 Generated with Claude Code

schani and others added 2 commits July 20, 2026 17:26
…ties (#1643)

quicktype only used patternProperties in place of additionalProperties when
the pattern key was the literal string ".*". Any other pattern (e.g.
"(^[A-Za-z0-9]+$)"), including ones that also match every string, was
silently ignored, leaving the property with no type information and
generating JSONAny/[String: JSONAny] instead of a typed map.

Generalize the existing hack: when additionalProperties is unset and
patternProperties has exactly one entry, use that entry's schema as the
effective additionalProperties, regardless of the pattern text. Multiple
patternProperties entries are left as before (unsupported).

Added a JSON Schema fixture (test/inputs/schema/pattern-properties.schema
+ .1.json) covering the reported shape, and a focused unit test asserting
the generated Swift output has a typed dictionary value instead of JSONAny.

Fixes #1643

Co-Authored-By: gpt-5.6-sol via pi <noreply@openai.com>
Co-Authored-By: Claude <noreply@anthropic.com>
schani and others added 4 commits July 20, 2026 20:30
Co-Authored-By: Claude <noreply@anthropic.com>
cjson does not validate class-element types, so the mistyped map value
in pattern-properties.1.fail.json is not rejected. Skip the schema for
cjson, matching the existing go-schema-pattern-properties handling.

Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown

Generated-output differences

26 files differ — 0 modified, 26 new, 0 deleted
2172 changed lines — +2172 / −0

Open the generated-output report →

@github-actions

Copy link
Copy Markdown

Generated-output differences

27 files differ — 0 modified, 27 new, 0 deleted
2242 changed lines — +2242 / −0

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.

JSON Schema not parsed correctly?

1 participant