Repository navigation
feat: camelCase query keys by default, with a naming policy - #8
Merged
Merged
Conversation
The expansion landed sending property names verbatim, reasoned from [Query]
parameters already sending theirs that way. Coherent, but compared against the
wrong neighbour: C# parameter names are camelCase by convention, so "verbatim"
only looks like camelCase there. The closer neighbour is body serialization,
which camelCases — so the same filter type sent as [Body] produced
{"state":...} while as [Query] it produced ?State=..., one library spelling one
type two ways. Since most APIs want the camelCase one, the old default cost an
attribute on nearly every property of every filter object, which is the
friction the feature existed to remove.
snake_case then makes a hardcoded flip the wrong shape, so this is a policy:
[QueryNaming] on the interface for the API that follows one convention
throughout — a Python or Ruby backend being the usual reason — overridable per
parameter and always beaten by an explicit [Query("name")].
It applies to parameter names as well as object properties. A policy that
transformed fields but left parameters alone would leave half a query string in
the other convention, which is worse than either consistent choice and is
exactly the trap someone setting snake_case would fall into. The default
changes nothing for parameters in practice, since C# parameter names are
already camelCase; AsDeclared is the one-attribute escape for anyone relying on
a non-camelCase one.
Word splitting follows what a reader expects rather than "uppercase starts a
word": an acronym run is one word and a digit run is its own, so HTTPReferer is
http_referer and not h_t_t_p_referer, and Top10Items is top_10_items. The
camelCase rule matches JsonNamingPolicy.CamelCase exactly, since matching it is
the whole justification for the default.
Applied in the generator, so every key is a literal in the emitted code and
nothing is transformed per request — AsDeclared is not a performance choice.
Dictionary keys are left alone, being data rather than a derived identifier.
Three existing tests asserted the old behaviour, including one whose entire
purpose was to pin it; they now pin the new default, and AsDeclared has its own
coverage. 1500 tests; build, format and native AOT clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Katyara11
force-pushed
the
feat/query-naming-policy
branch
from
November 7, 2026 11:45
7bd31fd to
d8dc2b0
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Query keys are camelCased from their identifiers by default, and a
[QueryNaming]policy coversAPIs following another convention —
snake_casefor a Python or Ruby backend being the usual case.Changes
QueryNamingenum,[QueryNaming]on an interface,[Query(Naming = …)]per parameter.CamelCase(default),SnakeCaseLower/Upper,KebabCaseLower/Upper,AsDeclared.defining-a-client.md, package README,CHANGELOG.Type
Checklist
Notes for review
Why the default changed. Expansion shipped sending property names verbatim, reasoned from
[Query]parameters doing the same. That compared against the wrong neighbour: C# parameter namesare camelCase by convention, so "verbatim" only looks like camelCase there. The closer neighbour
is body serialization, which camelCases — so the same type produced
{"state":…}as a[Body]and?State=…as a[Query]. Most APIs want the camelCase one, so the old default cost an attributeon nearly every property of every filter — the friction the feature existed to remove.
It applies to parameters too. A policy that transformed object fields but left parameter names
alone would leave half a query string in the other convention — worse than either consistent
choice, and the exact trap someone setting
snake_casewould fall into. In practice the defaultchanges nothing for parameters (C# names are already camelCase); a non-camelCase one now
camelCases, and
AsDeclaredis the one-attribute escape.Word splitting follows what a reader expects, not "uppercase starts a word":
HTTPRefererishttp_referer, noth_t_t_p_referer;Top10Itemsistop_10_items. The camelCase rule matchesJsonNamingPolicy.CamelCaseexactly, since matching it is the justification for the default.Compile-time. Applied in the generator, so every key is a literal in the emitted code —
AsDeclaredis not a performance choice. Dictionary keys are untouched, being data rather than aderived identifier.
Three existing tests asserted the old behaviour, one of them specifically to pin it. They now pin
the new default, with separate coverage for
AsDeclared.Verification
1500 tests;
--warnaserrorclean,dotnet formatclean, native AOT publishes and runs.