Skip to content

API DTOs: camelCase properties, snake_case JSON via the name converter (wire format unchanged) - #203

Merged
JanMikes merged 1 commit into
mainfrom
api/camel-case-dtos
Aug 19, 2026
Merged

API DTOs: camelCase properties, snake_case JSON via the name converter (wire format unchanged)#203
JanMikes merged 1 commit into
mainfrom
api/camel-case-dtos

Conversation

@JanMikes

Copy link
Copy Markdown
Member

The V1 DTOs had used snake_case public properties since the API was born — the one place outside the camelCase coding standard, because that is the JSON consumers get. Now the properties are camelCase and the snake_case wire format is produced by configuration:

  • api_platform.name_converter: serializer.name_converter.camel_case_to_snake_case — resources, request inputs (puzzle_id$puzzleId), OpenAPI schema, validation propertyPath;
  • ApiDtoNormalizer — the same converter for nested DTOs (statistics groups, cards inside lists, rating entries…), which Symfony's ObjectNormalizer would otherwise emit camelCase; scoped to SpeedPuzzling\Web\Api\* non-resource classes so nothing else in the app changes (a global framework converter would have renamed every other JSON);
  • phpcs.xml enforces camelCase in src/Api/ so it cannot creep back (verified: a probe with $puzzle_id fails, the tree passes).

Mechanical rename of 71 properties (declarations, ->fetches, named args, docblocks — string literals/array keys untouched) by a token-based script over src/Api, src/Services/Api, tests.

Proof of no change for consumers: OpenAPI export byte-identical before/after; the complete API test suite (asserts every JSON key) green unchanged; full suite 2183 green.

🤖 Generated with Claude Code

https://claude.ai/code/session_015uH2n4Y6gPLiYASEJwNr3H

…r (wire format unchanged)

The V1 DTOs had used snake_case public properties since the API was born
(puzzle_id, is_private, ...) because that is the JSON the consumers get - the
one place in the code base outside the camelCase coding standard. The
properties are now camelCase like everything else and the snake_case wire
format is produced by configuration:

- api_platform.name_converter = serializer.name_converter.camel_case_to_snake_case
  (resources, request inputs, the OpenAPI schema, violations' propertyPath);
- ApiDtoNormalizer applies the same converter to the nested DTOs (statistics
  groups, cards in a list, rating entries ...), which the framework serializer
  would otherwise emit camelCase - scoped to SpeedPuzzling\Web\Api\* non-resource
  classes, so nothing else in the application changes (a global
  framework.serializer name converter would have renamed every other JSON);
- phpcs enforces camelCase in src/Api (Squiz ValidVariableName NotCamelCaps +
  MemberNotCamelCaps, scoped there - elsewhere they only flag Stripe SDK
  members and fixture variables).

Mechanical rename of 71 properties across src/Api, src/Services/Api and the API
tests (declarations, ->fetches, named arguments, docblocks; string literals and
array keys - the JSON contract - untouched), by a token-based script.

Proof: the OpenAPI export is byte-identical before and after; the complete API
test suite, which asserts every JSON key, is green unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uH2n4Y6gPLiYASEJwNr3H
@JanMikes
JanMikes merged commit 5d2f0eb into main Aug 19, 2026
5 checks passed
@JanMikes
JanMikes deleted the api/camel-case-dtos branch August 19, 2026 17:42
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.

1 participant