diff --git a/packages/quicktype-core/src/input/JSONSchemaInput.ts b/packages/quicktype-core/src/input/JSONSchemaInput.ts index d3a0e4feef..5a931731e9 100644 --- a/packages/quicktype-core/src/input/JSONSchemaInput.ts +++ b/packages/quicktype-core/src/input/JSONSchemaInput.ts @@ -1139,6 +1139,14 @@ async function addTypesInSchema( additionalProperties = schema.patternProperties[".*"]; } + // Handle unevaluatedProperties if additionalProperties is not defined + if ( + additionalProperties === undefined && + schema.unevaluatedProperties !== undefined + ) { + additionalProperties = schema.unevaluatedProperties; + } + const objectAttributes = combineTypeAttributes( "union", inferredAttributes, diff --git a/test/inputs/schema/unevaluated-properties.1.fail.json b/test/inputs/schema/unevaluated-properties.1.fail.json new file mode 100644 index 0000000000..1b5ea63b1f --- /dev/null +++ b/test/inputs/schema/unevaluated-properties.1.fail.json @@ -0,0 +1,8 @@ +{ + "config": { + "name": "test-config", + "settings": { + "option1": "not-an-item-list" + } + } +} diff --git a/test/inputs/schema/unevaluated-properties.1.json b/test/inputs/schema/unevaluated-properties.1.json new file mode 100644 index 0000000000..a8e22fbc6a --- /dev/null +++ b/test/inputs/schema/unevaluated-properties.1.json @@ -0,0 +1,14 @@ +{ + "config": { + "name": "test-config", + "settings": { + "option1": [ + {"key": "foo", "value": "bar"}, + {"key": "baz", "value": "qux"} + ], + "option2": [ + {"key": "hello", "value": "world"} + ] + } + } +} \ No newline at end of file diff --git a/test/inputs/schema/unevaluated-properties.2.json b/test/inputs/schema/unevaluated-properties.2.json new file mode 100644 index 0000000000..a739f3e56c --- /dev/null +++ b/test/inputs/schema/unevaluated-properties.2.json @@ -0,0 +1,19 @@ +{ + "config": { + "name": "multiple-versions", + "settings": { + "v1.0.0": [ + {"key": "checksum1", "value": "abc123"}, + {"key": "checksum2", "value": "def456"} + ], + "v1.1.0": [ + {"key": "checksum1", "value": "ghi789"} + ], + "latest": [ + {"key": "checksum1", "value": "jkl012"}, + {"key": "checksum2", "value": "mno345"}, + {"key": "checksum3", "value": "pqr678"} + ] + } + } +} \ No newline at end of file diff --git a/test/inputs/schema/unevaluated-properties.schema b/test/inputs/schema/unevaluated-properties.schema new file mode 100644 index 0000000000..1e1382a192 --- /dev/null +++ b/test/inputs/schema/unevaluated-properties.schema @@ -0,0 +1,46 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "unevaluated-properties.schema", + "title": "Test unevaluatedProperties support", + "type": "object", + "properties": { + "config": { + "$ref": "#/$defs/Config" + } + }, + "$defs": { + "Config": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "settings": { + "$ref": "#/$defs/Settings" + } + } + }, + "Settings": { + "type": "object", + "properties": {}, + "unevaluatedProperties": { + "type": "array", + "items": { + "$ref": "#/$defs/Item" + } + } + }, + "Item": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": ["key", "value"] + } + } +} \ No newline at end of file diff --git a/test/languages.ts b/test/languages.ts index 6bcfd43611..94921b7593 100644 --- a/test/languages.ts +++ b/test/languages.ts @@ -46,6 +46,15 @@ const skipsUntypedUnions = [ "implicit-class-array-union.schema", ]; +// The generated code does not reject wrong-typed values in typed maps (a +// map accepts values that are not T), so the bare `.fail.json` +// samples for these map-valued schemas do not fail as expected. Add any new +// schema whose fail sample relies on rejecting a mistyped map value. +const skipsMapValueValidation = [ + "go-schema-pattern-properties.schema", + "unevaluated-properties.schema", +]; + export type LanguageFeature = | "enum" | "union" @@ -576,7 +585,7 @@ export const CJSONLanguage: Language = { ...skipsEnumValueValidation, /* Union, Map and Arrays with invalid types are not checked (for the current implementation, can be added later, should abord parsing and return NULL) */ "class-with-additional.schema", - "go-schema-pattern-properties.schema", + ...skipsMapValueValidation, "multi-type-enum.schema", "nested-intersection-union.schema", "prefix-items.schema", @@ -889,7 +898,7 @@ export const SwiftLanguage: Language = { "required.schema", "multi-type-enum.schema", "intersection.schema", - "go-schema-pattern-properties.schema", + ...skipsMapValueValidation, ...skipsEnumValueValidation, "date-time.schema", "class-with-additional.schema", @@ -1317,7 +1326,7 @@ export const KotlinLanguage: Language = { // instead of rejecting it. "nested-intersection-union.schema", "class-with-additional.schema", - "go-schema-pattern-properties.schema", + ...skipsMapValueValidation, // IllegalArgumentException "accessors.schema", "description.schema", @@ -1408,7 +1417,7 @@ export const KotlinJacksonLanguage: Language = { // instead of rejecting it. "nested-intersection-union.schema", "class-with-additional.schema", - "go-schema-pattern-properties.schema", + ...skipsMapValueValidation, // IllegalArgumentException "accessors.schema", "description.schema", @@ -1675,7 +1684,7 @@ export const PikeLanguage: Language = { // no implicit cast int <-> float in Pike ...skipsIntFloatUnions, // all below: not failing on expected failure. That's because Pike's quite tolerant with assignments. - "go-schema-pattern-properties.schema", + ...skipsMapValueValidation, "class-with-additional.schema", "multi-type-enum.schema", ...skipsUntypedUnions, @@ -1764,7 +1773,7 @@ export const HaskellLanguage: Language = { "prefix-items.schema", "direct-union.schema", ...skipsEnumValueValidation, - "go-schema-pattern-properties.schema", + ...skipsMapValueValidation, "intersection.schema", "multi-type-enum.schema", "keyword-unions.schema", @@ -1933,7 +1942,7 @@ export const TypeScriptZodLanguage: Language = { "constructor.schema", // z.coerce.date() serializes back as ISO UTC, not the input string "date-time.schema", - "go-schema-pattern-properties.schema", + ...skipsMapValueValidation, "intersection.schema", "multi-type-enum.schema", "keyword-unions.schema", @@ -2049,7 +2058,7 @@ export const TypeScriptEffectSchemaLanguage: Language = { ...skipsUntypedUnions, "direct-union.schema", ...skipsEnumValueValidation, - "go-schema-pattern-properties.schema", + ...skipsMapValueValidation, "intersection.schema", "multi-type-enum.schema", "keyword-unions.schema", @@ -2116,7 +2125,7 @@ export const ElixirLanguage: Language = { // The test incorrectly succeeds due to the emitter being permissive for unions that contain only primitives. A future enhancement // for the Elixir emitter could be a user-controlled 'strict' mode that pattern matches even on unions of only primitive types. - "go-schema-pattern-properties.schema", + ...skipsMapValueValidation, // The generated top-level type is not emitted as a TopLevel module the fixture can call. "recursive-union-flattening.schema",