From a7542bd1e6f65f86bd127a8fb34f27f0ddd3e403 Mon Sep 17 00:00:00 2001 From: Mark Probst Date: Mon, 20 Jul 2026 20:40:02 -0400 Subject: [PATCH] fix(typescript-input): preserve declared property order (#898) typescript-json-schema was invoked without propOrder enabled, so generated JSON Schemas had no property-order metadata and downstream languages (e.g. Swift) alphabetized fields instead of preserving the TypeScript declaration order. Enable propOrder in the typescript-json-schema settings and copy the resulting propertyOrder metadata onto quicktype's existing quicktypePropertyOrder field, which JSONSchemaInput already honors. Covered by a new unit test in test/unit/typescript-input.test.ts that compiles a TypeScript class through to Swift output and asserts property order is preserved; this path (TypeScript source as input) isn't reachable through the JSON/JSON Schema fixture harness, which starts from JSON/schema files rather than TS sources. Co-Authored-By: gpt-5.6-sol via pi --- .../quicktype-typescript-input/src/index.ts | 13 +++++-- test/unit/typescript-input.test.ts | 35 +++++++++++++++++-- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/packages/quicktype-typescript-input/src/index.ts b/packages/quicktype-typescript-input/src/index.ts index e29f49118f..dc3664f7cf 100644 --- a/packages/quicktype-typescript-input/src/index.ts +++ b/packages/quicktype-typescript-input/src/index.ts @@ -20,6 +20,7 @@ const settings: PartialArgs = { titles: true, topRef: true, noExtraProps: true, + propOrder: true, }; const compilerOptions: ts.CompilerOptions = { @@ -186,12 +187,20 @@ export function schemaForTypeScriptSources( if ( definition === null || Array.isArray(definition) || - typeof definition !== "object" || - typeof definition.description !== "string" + typeof definition !== "object" ) { continue; } + if (Array.isArray(definition.propertyOrder)) { + (definition as Record).quicktypePropertyOrder = + definition.propertyOrder; + } + + if (typeof definition.description !== "string") { + continue; + } + const description = definition.description as string; const matches = /#TopLevel/.exec(description); if (matches === null) { diff --git a/test/unit/typescript-input.test.ts b/test/unit/typescript-input.test.ts index 2e5f6a8842..6851d512bd 100644 --- a/test/unit/typescript-input.test.ts +++ b/test/unit/typescript-input.test.ts @@ -1,6 +1,7 @@ import * as fs from "node:fs"; import * as path from "node:path"; +import { InputData, JSONSchemaInput, quicktype } from "quicktype-core"; import { schemaForTypeScriptSources } from "quicktype-typescript-input"; import { afterAll, describe, expect, test } from "vitest"; @@ -18,13 +19,17 @@ afterAll(() => { let uniqueFileIndex = 0; -function schemaForSource(source: string) { +function schemaSourceForSource(source: string) { const fileName = path.join( path.relative(process.cwd(), temporaryDirectory), `input-${uniqueFileIndex++}.ts`, ); fs.writeFileSync(fileName, source); - const result = schemaForTypeScriptSources([fileName]); + return schemaForTypeScriptSources([fileName]); +} + +function schemaForSource(source: string) { + const result = schemaSourceForSource(source); return { name: result.name ?? "", schema: JSON.parse(result.schema), @@ -32,7 +37,33 @@ function schemaForSource(source: string) { }; } +async function swiftForSource(source: string): Promise { + const schemaInput = new JSONSchemaInput(undefined); + await schemaInput.addSource(schemaSourceForSource(source)); + + const inputData = new InputData(); + inputData.addInput(schemaInput); + + const result = await quicktype({ inputData, lang: "swift" }); + return result.lines.join("\n"); +} + describe("schemaForTypeScriptSources", () => { + test("preserves class property declaration order in generated output", async () => { + const output = await swiftForSource(` + class A { + name: string; + age: number; + } + `); + + const nameIndex = output.indexOf("let name: String"); + const ageIndex = output.indexOf("let age: Double"); + expect(nameIndex).toBeGreaterThanOrEqual(0); + expect(ageIndex).toBeGreaterThanOrEqual(0); + expect(nameIndex).toBeLessThan(ageIndex); + }); + test("converts a simple interface", () => { const { schema, uris } = schemaForSource(` export interface Person {