Skip to content

fix(schema): honor patternProperties value schema for object maps - #2996

Open
schani wants to merge 9 commits into
masterfrom
agent/fix-issue-1854
Open

fix(schema): honor patternProperties value schema for object maps#2996
schani wants to merge 9 commits into
masterfrom
agent/fix-issue-1854

Conversation

@schani

@schani schani commented Jul 20, 2026

Copy link
Copy Markdown
Member

Bug

quicktype discarded the value schema of JSON Schema patternProperties maps. In makeObjectType (packages/quicktype-core/src/input/JSONSchemaInput.ts), a schema's patternProperties was only ever consulted when it had a literal ".*" key — a narrow hack originally added for a Go->Schema generator quirk (#976). Any other regex pattern (e.g. "^.*$", or the real-world "^.*\S.*$" from this issue) was silently ignored, so the property fell back to an untyped Dictionary<string, object> instead of a proper generated class, even though the exact same schema written with additionalProperties instead of patternProperties already worked correctly.

Repro (before fix)

{
    "type": "object",
    "properties": {
        "Materials": {
            "type": "object",
            "patternProperties": {
                "^.*$": {
                    "type": "object",
                    "properties": {
                        "roughness": { "type": "string" },
                        "thickness": { "type": "number" }
                    },
                    "required": ["roughness", "thickness"]
                }
            }
        }
    },
    "required": ["Materials"]
}
node dist/index.js --src-lang schema --lang cs pattern.json

produced:

public partial class Pattern
{
    [JsonPropertyName("Materials")]
    public Dictionary<string, object> Materials { get; set; }
}

(no Materials/value class generated at all, plus a warning that quicktype "cannot infer this type").

Fix

When additionalProperties is absent and patternProperties is a non-empty object, its value schema(s) are now used to type the map's values — a single pattern's schema is used directly, and multiple patterns are combined with anyOf — matching how additionalProperties is already handled. This doesn't attempt full JSON Schema patternProperties regex-matching semantics (nothing in quicktype validates property names against the pattern); it just stops discarding the type information, consistent with how additionalProperties is already treated as "the type of any additional map values."

After the fix, the same repro produces:

public partial class Pattern
{
    [JsonPropertyName("Materials")]
    public Dictionary<string, Material> Materials { get; set; }
}

public partial class Material
{
    [JsonPropertyName("roughness")]
    public string Roughness { get; set; }

    [JsonPropertyName("thickness")]
    public double Thickness { get; set; }
}

Test coverage

  • New JSON Schema fixture: test/inputs/schema/pattern-properties-value.schema + .1.json, exercising a non-.* patternProperties pattern with an object value schema. It's picked up automatically by the existing schema-input fixture discovery (test/fixtures.ts) for every registered JSONSchemaFixture language.
  • New unit test test/unit/pattern-properties-schema.test.ts asserting the generated C# output has the typed Dictionary<string, Material> and the Material class with Roughness/Thickness properties (fixture round-trip tests can't distinguish a typed map value from object, since both round-trip the same JSON).
  • Confirmed the two pre-existing related fixtures still pass unchanged: go-schema-pattern-properties.schema (the narrow .*-key case) and class-with-additional.schema (the additionalProperties case).

Verification performed locally

  • npm run build passes.
  • npm run test:unit: 164/164 tests pass.
  • QUICKTEST=true FIXTURE=schema-golang script/test: 68/68 tests pass (includes the new fixture plus the two regression fixtures above).
  • Manually re-ran the repro against the built dist/index.js for --lang cs, confirming correct output (shown above), and also against the exact pattern from the issue (^.*\S.*$) with the full property set — matches the issue's expected output.
  • CI will additionally validate the new fixture across all other registered schema-input languages (C#, Java, Python, Rust, Ruby, PHP, Swift, TypeScript, Kotlin, etc.), whose toolchains weren't all available locally.

Fixes #1854

🤖 Generated with Claude Code

schani and others added 2 commits July 20, 2026 17:12
)

quicktype only recognized `patternProperties` as a map-value type when it
had a literal `.*` key (a hack for a Go->Schema generator quirk). Any other
regex pattern, including the ones real-world schemas actually use, was
silently discarded, so the map fell back to an untyped `Dictionary<string,
object>` instead of generating a proper value class.

Now, when `additionalProperties` is absent, any non-empty `patternProperties`
schema(s) are used to type the map values (multiple patterns are combined
with `anyOf`), matching how `additionalProperties` already behaves.

Added a JSON Schema fixture (pattern-properties-value.schema/.1.json) and a
unit test asserting the generated C# class, and verified the existing
go-schema-pattern-properties and class-with-additional fixtures still pass.

Co-Authored-By: gpt-5.6-sol via pi <noreply@openai.com>
schani and others added 4 commits July 20, 2026 20:26
cJSON does not validate map/patternProperties value types, so the
negative case pattern-properties-value.1.fail.json was round-tripped
instead of rejected. Skip the schema for cjson, alongside the sibling
go-schema-pattern-properties.schema which is skipped for the same reason.
The positive and negative cases still run (and the negative is genuinely
rejected) for the other languages.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Resolve test/languages.ts conflict: master refactored the per-language
map-value skip into the shared skipsMapValueValidation constant. Move the
new pattern-properties-value.schema into that constant (alongside its
sibling go-schema-pattern-properties.schema) so every map-value-lenient
language skips its negative case, not just cjson. The positive and negative
cases still run for the strict languages (verified: Python rejects the
mistyped map value and accepts the valid sample).

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
2069 changed lines — +2069 / −0

Open the generated-output report →

@github-actions

Copy link
Copy Markdown

Generated-output differences

27 files differ — 0 modified, 27 new, 0 deleted
2139 changed lines — +2139 / −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.

Complex schema issues

1 participant