fix(schema): infer patternProperties value type for any single pattern#3027
Open
schani wants to merge 8 commits into
Open
fix(schema): infer patternProperties value type for any single pattern#3027schani wants to merge 8 commits into
schani wants to merge 8 commits into
Conversation
#1464) The additionalProperties/patternProperties hack added for #976 only kicked in when the pattern was literally ".*", so any other single regex (e.g. "^[a-zA-Z]\w*$") fell back to an untyped map value (nlohmann::json/any) instead of the schema's declared type. Generalize the hack to apply whenever patternProperties has exactly one entry, regardless of the pattern text, since quicktype does not validate property names against the pattern anyway. Co-Authored-By: gpt-5.6-sol via pi <noreply@openai.com>
The typescript-zod renderer emits no named schema export for a top-level map type, so the zod test driver throws 'No schema found'. This is a pre-existing zod limitation the new pattern-properties.schema fixture (a top-level map) exposes; the plain typescript renderer handles it fine. Scope the fixture out for zod. Co-Authored-By: Claude <noreply@anthropic.com>
cjson does not validate patternProperties/map value types against the input on parse (a known, documented limitation shared with go-schema-pattern-properties.schema), so the new negative test case never fails as expected. It also crashes (SIGSEGV) when printing a scalar (bool) map value produced from this schema, since the map/array item-printing code only special-cases string and object/union values. Skip the new fixture for cjson, matching the existing precedent for the same class of limitation, until that support is added. Co-Authored-By: gpt-5.6-sol via pi <noreply@openai.com>
# Conflicts: # test/languages.ts
Java deserializes a top-level map via a raw `Map.class` reader (javaTypeWithoutGenerics drops the value type), so the Boolean map value type is not enforced on parse and pattern-properties.1.fail.json round-trips instead of failing. Skip the schema for Java; nested typed maps keep their value-validation coverage elsewhere, and the schema's positive and negative cases still run on other languages. Co-Authored-By: Claude <noreply@anthropic.com>
…aren't enforced
The new pattern-properties.schema is a top-level map whose fail sample
`{"enabled": {}}` must be rejected. Languages that deserialize a map
without validating the value type (unchecked generic casts, raw map
readers, or no runtime type checks) round-trip it instead of failing, so
add pattern-properties.schema to the shared skipsMapValueValidation list.
That consolidates the previously per-language cjson and zod skips.
PHP additionally can't run the positive case: it has no type aliases, so
a top-level map produces no named TopLevel class and the driver's
TopLevel::from() call fails. Skip it there for the same reason top-level
unions, enums, and arrays are already skipped for PHP.
Positive and negative cases still run on many validating languages
(Go, Rust, Python, TypeScript, JavaScript, Flow, C#, C++, Scala, Elm,
Dart, KotlinX, schema-to-schema).
Co-Authored-By: Claude <noreply@anthropic.com>
Generated-output differences16 files differ — 0 modified, 16 new, 0 deleted |
Generated-output differences17 files differ — 0 modified, 17 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
patternPropertiesin JSON Schema was only correctly narrowed to thedeclared value type for the specific pattern
".*". Any other singleregex (e.g.
^[a-zA-Z]\w*$) fell back to an untyped map value (e.g.nlohmann::json/anyin C++), even though the schema explicitlydeclared the property type.
Repro:
{ "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "patternProperties": { "^[a-zA-Z]\\w*$": { "type": "boolean" } } }Before:
using Schema = std::map<std::string, nlohmann::json>;Expected/after:
Root cause
packages/quicktype-core/src/input/JSONSchemaInput.tshas a hack(originally added for #976) that treats
patternPropertiesasadditionalProperties— but only when the pattern is the literal string".*":Any other pattern (regardless of how narrow or specific) was ignored, so
the value type was never inferred from it.
Fix
Generalized the check to apply whenever
patternPropertieshas exactlyone entry, using that entry's schema as
additionalPropertiesregardless of what the pattern string actually is. quicktype does not
validate property names against the pattern anyway, and with only one
pattern there's no ambiguity about which schema to use for the map's
value type. The pre-existing
.*special case (and its fixture,go-schema-pattern-properties.schema, covering #976) continues to workunchanged since
.*is itself just a single-entry case.Schemas with more than one
patternPropertiesentry are unaffected andcontinue to fall back to
any, as before — this change does not attemptper-pattern-key typing for that case.
Test coverage
Added
test/inputs/schema/pattern-properties.schema(the schema fromthe issue) with:
pattern-properties.1.json— a valid sample ({"enabled": true})pattern-properties.1.fail.json— an invalid sample ({"enabled": {}})that must fail to parse once the value type is correctly narrowed to
booleanThis is an end-to-end JSON Schema fixture per the repo's testing
conventions (
test/inputs/schema/), auto-discovered by the existingschema fixture machinery — no changes to
test/languages.ts/test/fixtures.tswere needed.Verification
npm run buildpasses.std::map<std::string, nlohmann::json>with an "cannot infer this type" warning) and confirmed the fix producesstd::map<std::string, bool>with no warning.QUICKTEST=true FIXTURE=schema-typescript script/test test/inputs/schema/pattern-properties.schema) — passes (both the positive and negative sample)..*-pattern fixture (go-schema-pattern-properties.schema, for Infer property type based on patternProperties if pattern is .* #976) still generates the correct typed map (std::map<std::string, int64_t>), i.e. no regression.schema-typescriptfixture suite; it surfaced one unrelated pre-existing failure (vega-lite.schema, a TypeScript index-signature type error) that I confirmed also fails identically on unmodified master (nopatternPropertiesinvolved in that schema at all) — not caused by this change.boost: true), which is unrelated to this fix; CI will validate it.Fixes #1464
🤖 Generated with Claude Code