Skip to content

fix(rendering): preserve enum case source order#3051

Merged
schani merged 3 commits into
masterfrom
agent/fix-issue-2401
Jul 21, 2026
Merged

fix(rendering): preserve enum case source order#3051
schani merged 3 commits into
masterfrom
agent/fix-issue-2401

Conversation

@schani

@schani schani commented Jul 20, 2026

Copy link
Copy Markdown
Member

The bug

Enum cases were always rendered in alphabetical order, regardless of the
order they appear in the source JSON Schema (or JSON samples). For example:

$ cat day.schema.json
{"$schema":"http://json-schema.org/draft-07/schema#","type":"string","enum":["Monday","Tuesday","Friday","Sunday"]}

$ quicktype --lang csharp --src-lang schema day.schema.json
public enum DaySchemaEnum { Friday, Monday, Sunday, Tuesday };

instead of the expected declaration order Monday, Tuesday, Friday, Sunday.
This affected every generated language (~29 renderers), since they all share
the same code path. While JSON round-tripping still worked (because
(de)serializers match by string name), anything relying on positional/ordinal
semantics — e.g. C#'s implicit underlying int enum values — broke, along
with unnecessary diff noise whenever a schema's enum order changed.

Root cause

ConvenienceRenderer.forEachEnumCase in
packages/quicktype-core/src/ConvenienceRenderer.ts unconditionally ran the
already source-ordered case names through mapSortBy, alphabetizing them
before rendering. A second, related spot in
packages/quicktype-core/src/MakeTransformations.ts (makeEnumTransformer)
also alphabetically sorted enumType.cases before building the enum's
runtime string-match transformer (used e.g. by the TypeScript/Flow "Convert"
helpers), for the same reason.

The fix

Removed both forced alphabetical sorts, since EnumType.cases (and the
derived case-name map) already preserve the source's insertion order:

  • ConvenienceRenderer.forEachEnumCase now iterates caseNames directly
    instead of calling mapSortBy on it.
  • MakeTransformations.makeEnumTransformer now maps over
    Array.from(enumType.cases) directly instead of sorting it first.

This is a default-behavior fix (no new renderer option), consistent with
other breaking-default fixes already sanctioned for the next major release.

Test coverage

  • Added test/unit/enum-order.test.ts, a focused regression test that
    reproduces the exact issue Enums are ALWAYS sorted #2401 repro via the quicktype API and asserts
    the generated C# enum preserves declaration order
    (Monday, Tuesday, Friday, Sunday). This test fails on unfixed code and
    passes with the fix.
  • Extended the C# fixture driver (test/fixtures/csharp/Program.cs) to
    assert, when running the existing test/inputs/schema/enum.schema +
    enum.1.json fixture sample, that the generated QuickType.cs contains
    public enum Lvc { Lawful, Neutral, Chaotic }; (the schema's declared
    ["lawful", "neutral", "chaotic"] order, which is not alphabetical) rather
    than a sorted order. This exercises the fix through the existing
    schema-csharp fixture pipeline without needing new schema/sample files.

Verification performed locally

  • npm run build passes.
  • npm run test:unit: all 177 unit tests pass, including the new
    enum-order.test.ts.
  • Manually re-ran the original repro for C#, TypeScript, Python, Go, Java,
    Kotlin, Swift, and Rust — all now emit enum cases in source declaration
    order.
  • The dotnet toolchain is not available in this environment, so the full
    schema-csharp end-to-end fixture (which exercises the new
    Program.cs assertion) could not be executed locally; it will be
    validated by CI.

Fixes #2401.

🤖 Generated with Claude Code

schani and others added 2 commits July 20, 2026 19:13
Co-Authored-By: gpt-5.6-sol via pi <noreply@openai.com>
Comment thread test/fixtures/csharp/Program.cs Outdated
var json = System.IO.File.ReadAllText(path);
var output = TopLevel.FromJson(json).ToJson();

if (System.IO.Path.GetFileName(path) == "enum.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.

Wow, this is a super shitty way of testing! The fixture should not be special-cased for specific test cases! Find a principled way of doing this!

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.

Agreed, that was the wrong place for it. I removed the filename-keyed check from the C# fixture driver in dfa4681.

The fixture harness is a JSON round-trip (deserialize, reserialize, compare), and enum case source ordering is invisible to a string round-trip, so a fixture genuinely can't express this behavior. That ordering is instead asserted directly and portably in test/unit/enum-order.test.ts, which checks the generated C# and C++ enum declarations preserve schema order through the quicktype API. The fixture driver is back to its original form with no special cases.

The C# fixture driver was special-cased to inspect the generated
QuickType.cs source whenever the sample filename was enum.1.json. The
fixture pipeline is a round-trip harness (deserialize then reserialize
and compare JSON); wiring a filename-keyed source assertion into the
driver is not something it is built for, and no other language driver
does anything like it.

Enum case source ordering is invisible to a string round-trip anyway, so
the fixture cannot express it. That behavior is already covered directly
and portably by test/unit/enum-order.test.ts, which asserts the generated
C# and C++ enum declarations preserve schema order via the quicktype API.

Co-Authored-By: Claude <noreply@anthropic.com>
@schani
schani merged commit acd8d7b into master Jul 21, 2026
28 checks passed
@schani
schani deleted the agent/fix-issue-2401 branch July 21, 2026 19:40
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.

Enums are ALWAYS sorted

1 participant