diff --git a/packages/quicktype-core/src/input/JSONSchemaInput.ts b/packages/quicktype-core/src/input/JSONSchemaInput.ts index 38d451ffa..a655abf2f 100644 --- a/packages/quicktype-core/src/input/JSONSchemaInput.ts +++ b/packages/quicktype-core/src/input/JSONSchemaInput.ts @@ -825,13 +825,14 @@ async function addTypesInSchema( properties: StringMap, requiredArray: string[], additionalProperties: unknown, - sortKey: (k: string) => number | string = (k: string): string => - k.toLowerCase(), + sortKey?: (k: string) => number | string, ): Promise { const required = new Set(requiredArray); - const propertiesMap = mapSortBy(mapFromObject(properties), (_, k) => - sortKey(k), - ); + const unsortedPropertiesMap = mapFromObject(properties); + const propertiesMap = + sortKey === undefined + ? unsortedPropertiesMap + : mapSortBy(unsortedPropertiesMap, (_, k) => sortKey(k)); const props = await mapMapSync( propertiesMap, async (propSchema, propName) => { @@ -1164,15 +1165,15 @@ async function addTypesInSchema( inferredAttributes, combineProducedAttributes(({ forObject }) => forObject), ); - const order = schema.quicktypePropertyOrder - ? schema.quicktypePropertyOrder - : []; - const orderKey = (propertyName: string): string => { - // use the index of the order array - const index = order.indexOf(propertyName); - // if no index then use the property name - return index !== -1 ? index : propertyName.toLowerCase(); - }; + const order = schema.quicktypePropertyOrder; + const orderKey = order + ? (propertyName: string): string => { + // use the index of the order array + const index = order.indexOf(propertyName); + // if no index then use the property name + return index !== -1 ? index : propertyName.toLowerCase(); + } + : undefined; return await makeObject( loc, diff --git a/test/fixtures/typescript/main.ts b/test/fixtures/typescript/main.ts index c22518dd2..b955f5282 100644 --- a/test/fixtures/typescript/main.ts +++ b/test/fixtures/typescript/main.ts @@ -10,4 +10,16 @@ const json = fs.readFileSync(sample); const value = TopLevel.Convert.toTopLevel(json); const backToJson = TopLevel.Convert.topLevelToJson(value); +if (sample.endsWith("property-order.1.json")) { + const input = JSON.parse(json.toString()); + const output = JSON.parse(backToJson); + const keys = (object: object) => JSON.stringify(Object.keys(object)); + if ( + keys(input) !== keys(output) || + keys(input.ordered) !== keys(output.ordered) + ) { + throw new Error("Generated property order does not match the schema"); + } +} + console.log(backToJson); diff --git a/test/inputs/schema/property-order.1.fail.no-defaults.json b/test/inputs/schema/property-order.1.fail.no-defaults.json new file mode 100644 index 000000000..7addfdb86 --- /dev/null +++ b/test/inputs/schema/property-order.1.fail.no-defaults.json @@ -0,0 +1,11 @@ +{ + "zebra": "stripes", + "mango": 1.5, + "apple": true, + "delta": "change", + "ordered": { + "mango": 2.5, + "zebra": "stripes", + "apple": false + } +} diff --git a/test/inputs/schema/property-order.1.json b/test/inputs/schema/property-order.1.json new file mode 100644 index 000000000..5e65476d1 --- /dev/null +++ b/test/inputs/schema/property-order.1.json @@ -0,0 +1,12 @@ +{ + "zebra": "stripes", + "mango": 1.5, + "apple": true, + "delta": "change", + "banana": 3, + "ordered": { + "mango": 2.5, + "zebra": "stripes", + "apple": false + } +} diff --git a/test/inputs/schema/property-order.schema b/test/inputs/schema/property-order.schema new file mode 100644 index 000000000..64a765223 --- /dev/null +++ b/test/inputs/schema/property-order.schema @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "zebra": { "type": "string" }, + "mango": { "type": "number" }, + "apple": { "type": "boolean" }, + "delta": { "type": "string" }, + "banana": { "type": "integer" }, + "ordered": { + "type": "object", + "quicktypePropertyOrder": ["mango", "zebra"], + "properties": { + "zebra": { "type": "string" }, + "mango": { "type": "number" }, + "apple": { "type": "boolean" } + }, + "required": ["zebra", "mango", "apple"], + "additionalProperties": false + } + }, + "required": ["zebra", "mango", "apple", "delta", "banana", "ordered"], + "additionalProperties": false +} diff --git a/test/languages.ts b/test/languages.ts index 8992f3bb4..cc6efaa97 100644 --- a/test/languages.ts +++ b/test/languages.ts @@ -679,6 +679,7 @@ export const CJSONLanguage: Language = { /* Required properties absent are not checked (for the current implementation, can be added later, should abord parsing and return NULL) */ "ie-suffix-singularization.schema", "intersection.schema", + "property-order.schema", "required.schema", // The default-value fail sample also relies on required-property // enforcement, which cJSON does not do. @@ -1935,6 +1936,7 @@ export const HaskellLanguage: Language = { "keyword-unions.schema", "optional-any.schema", "ie-suffix-singularization.schema", + "property-order.schema", "required.schema", // The default-value fail sample also relies on required-property enforcement. "default-value.schema", @@ -2308,6 +2310,7 @@ export const ElixirLanguage: Language = { "top-level-primitive-array.schema", "boolean-subschema.schema", "intersection.schema", + "property-order.schema", "optional-any.schema", // The test incorrectly succeeds due to the emitter being permissive for unions that contain only primitives. A future enhancement