I would like to smoothly transition our frontend from using snake case to camel case for request and response bodies.
Currently, I support both camel and snake cases simultaneously based on a header provided in the request. The important requirement is that if the request is in snake case, the response must also be in snake case, and the same applies to camel case.
Example:
header: {"App-Case": "snake"}, request: {"user_id": ...}, response: {"user_id: ...}
header: {"App-Case": "camel"}, request: {"userId": ...}, response: {"userId: ...}
I have achieved this by using API routes:
class AppCase(str, Enum):
CAMEL = "camel"
SNAKE = "snake"
class CamelCaseRoute(APIRoute):
"""Route class to decamelize requests and camelize responses."""
current_handler_name: str = "app"
def get_route_handler(self) -> Callable:
"""Override default route handler getter."""
original_route_handler = super().get_route_handler()
async def custom_route_handler(request: Request) -> Response:
"""Handle route taking into account app-case query param."""
CamelCaseRoute.current_handler_name = self.endpoint.__name__
case = AppCase(request.headers.get("App-Case", "snake"))
if case is AppCase.SNAKE:
return await original_route_handler(request)
request = await self._decamelize_request_body(request=request) # Convert camel to snake for request body
response = await original_route_handler(request)
return self._camelize_response(response=response) # Convert snake to camel for response body
return custom_route_handler
# And then use it in following way
router = APIRouter(route_class=CamelCaseRoute)
While this solution works perfectly, I wonder if there is a cleaner and more concise way to achieve my goals.
I have also considered creating a “v2” API for camel case input/output and leaving “v1” to work with snake case as it does currently.