Skip to content

fix(schema): preserve property declaration order from JSON Schema#3016

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

fix(schema): preserve property declaration order from JSON Schema#3016
schani wants to merge 9 commits into
masterfrom
agent/fix-issue-2698

Conversation

@schani

@schani schani commented Jul 20, 2026

Copy link
Copy Markdown
Member

The bug

alphabetizeProperties: false (the default) had no effect on JSON Schema
input — generated properties were always alphabetized, regardless of the
option. The equivalent JSON (non-schema) input correctly preserved
declaration order.

Repro:

node dist/index.js --lang typescript --src-lang schema -t TopLevel schema.json --just-types

with a schema declaring properties zebra, mango, apple, delta, banana
produced apple, banana, delta, mango, zebra (alphabetized) instead of
the schema's declaration order.

Root cause

makeObject in packages/quicktype-core/src/input/JSONSchemaInput.ts
defaulted its sortKey parameter to (k) => k.toLowerCase() and always
ran properties through mapSortBy, discarding the schema's declared
property order at type-building time — before the renderer-level
alphabetizeProperties option (in ConvenienceRenderer.ts) ever got a
chance to apply. That renderer option only ever adds alphabetical
sorting when explicitly set to true; it has no way to recover an order
that was already thrown away upstream. JSON (non-schema) input never went
through this code path, so it worked correctly.

The fix

sortKey is now optional. When no sort key is supplied (the common case),
properties keep their original object insertion order (which reflects
schema declaration order). The one place that already computed a sort key
— the quicktypePropertyOrder schema extension — continues to work
exactly as before. alphabetizeProperties: true continues to sort at
render time, unaffected by this change.

Test coverage

Added test/inputs/schema/property-order.schema with a corresponding
property-order.1.json sample:

  • top-level properties declared out of alphabetical order, to verify
    default declaration-order preservation
  • a nested ordered object using the quicktypePropertyOrder extension,
    to verify that mechanism still works

The typescript fixture driver (test/fixtures/typescript/main.ts) was
extended to assert, for this specific sample, that the generated code's
round-tripped JSON key order matches the input's key order — this is the
only fixture-level way to observe property order rather than just
round-trip value correctness.

Verification

  • Added the fixture test first and confirmed it failed against the
    pre-fix code (alphabetized order didn't match expected declaration
    order).
  • Applied the fix; re-ran and confirmed it passes.
  • Manually re-ran the original repro from the issue — output now
    preserves declaration order (zebra, mango, apple, delta, banana).
  • Manually verified --alphabetize-properties still alphabetizes as
    expected.
  • npm run build passes.
  • QUICKTEST=true FIXTURE=schema-typescript script/test — all 71 tests
    pass, including the new property-order.schema fixture.
  • Did not run the full multi-language fixture suite locally; CI will
    cover the remaining languages.

Fixes #2698.

🤖 Generated with Claude Code

schani and others added 7 commits July 20, 2026 17:44
)

JSON Schema input always sorted object properties by lowercased name at
type-building time in makeObject (JSONSchemaInput.ts), discarding the
schema's declaration order before the alphabetizeProperties renderer
option ever got a chance to apply. JSON (non-schema) input was
unaffected since it doesn't go through this schema-specific sort.

Make sortKey optional in makeObject and only sort when a key is given
(quicktypePropertyOrder still supplies one when the schema declares it).
Otherwise properties keep their original insertion order, matching JSON
input, and alphabetizeProperties:true continues to sort at render time.

Adds a schema fixture (property-order.schema/.1.json) covering both
default declaration order and quicktypePropertyOrder, verified via a
generated-vs-source key-order check in the typescript fixture driver.

Co-Authored-By: gpt-5.6-sol via pi <noreply@openai.com>
Co-Authored-By: Claude <noreply@anthropic.com>
…sing required properties (#undefined)

cjson silently defaults a missing required field to 0 instead of
failing, the same known limitation already documented for
required.schema and intersection.schema. This matched CI's
property-order.1.fail.no-defaults.json exiting 0 instead of failing as
expected.

Co-Authored-By: gpt-5.6-sol via pi <noreply@openai.com>
Brings the PR branch up to date with master (which the pull_request CI
check merges against). Also fixes the one regression the merge
surfaces: test/unit/cjson-enum-default.test.ts (added on master after
this branch diverged) hardcoded alphabetical cJSON enum member order.
This branch's fix (issue #2698) intentionally preserves JSON Schema
declaration order instead, so the generated enum for the test's schema
now correctly emits members as state, config, heartbeat (declaration
order) rather than config, heartbeat, state (alphabetical) - the
sentinel-avoidance numbering (starting at 1) is unchanged. Updated the
test's expected member order to match.

Co-Authored-By: gpt-5.6-sol via pi <noreply@openai.com>
…ot detect missing required properties (#undefined)

Same root cause as the earlier cjson fix on this branch: both
languages already skip required.schema/intersection.schema for the
identical documented reason (Elixir sets absent struct keys to null
at runtime; Haskell's fixture driver serializes a failed Maybe decode
as JSON null and exits 0), so property-order.1.fail.no-defaults.json
(omits required "banana", no default) cannot be detected as a failure
by either.

Co-Authored-By: gpt-5.6-sol via pi <noreply@openai.com>
Resolves additive conflict in test/languages.ts Elixir skipSchema list: keep both property-order.schema (this branch) and optional-any.schema (master).

Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown

Generated-output differences

840 files differ — 809 modified, 31 new, 0 deleted
25873 changed lines — +14475 / −11398

Open the generated-output report →

@github-actions

Copy link
Copy Markdown

Generated-output differences

871 files differ — 839 modified, 32 new, 0 deleted
26541 changed lines — +14853 / −11688

Open the generated-output report →

const value = TopLevel.Convert.toTopLevel(json);
const backToJson = TopLevel.Convert.topLevelToJson(value);

if (sample.endsWith("property-order.1.json")) {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a shit way of testing this. The fixtures should never special-case for specific test cases!!! Find a clean way of testing this!

@github-actions

Copy link
Copy Markdown

Generated-output differences

872 files differ — 840 modified, 32 new, 0 deleted
26547 changed lines — +14856 / −11691

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.

[BUG]: alphabetizeProperties option does not work

1 participant