Python+Flask REST API如何实现数据键在驼峰式与蛇形命名间转换?
嘿,这个需求我太熟了!刚好结合你在用的Flask+SQLAlchemy+Marshmallow技术栈,有个非常贴合的最优方案,还有备用方案给你参考:
最优方案:基于Marshmallow Schema的自动转换
既然你已经在用Marshmallow做序列化/反序列化,那直接在Schema层面实现键名转换是最精准、最贴合现有技术栈的方案——完全不需要全局修改请求响应逻辑,只在数据序列化的环节处理,不会干扰其他业务。
步骤1:定义基础Schema类
先写一个通用的BaseSchema,内置camelCase转snake_case(接收前端数据时)和snake_case转camelCase(返回响应时)的递归转换逻辑:
from marshmallow import Schema, pre_load, post_dump class BaseSchema(Schema): def _camel_to_snake(self, data): # 递归处理字典、列表和嵌套结构 if isinstance(data, dict): converted = {} for key, value in data.items(): # 把camelCase转成snake_case:比如userName → user_name snake_key = ''.join(['_' + c.lower() if c.isupper() else c for c in key]).lstrip('_') converted[snake_key] = self._camel_to_snake(value) return converted elif isinstance(data, list): return [self._camel_to_snake(item) for item in data] else: return data def _snake_to_camel(self, data): # 递归处理字典、列表和嵌套结构 if isinstance(data, dict): converted = {} for key, value in data.items(): # 把snake_case转成camelCase:比如user_name → userName camel_key = ''.join([word.capitalize() if i > 0 else word for i, word in enumerate(key.split('_'))]) converted[camel_key] = self._snake_to_camel(value) return converted elif isinstance(data, list): return [self._snake_to_camel(item) for item in data] else: return data @pre_load def handle_incoming_data(self, data, **kwargs): # 前端发来的camelCase数据,预处理转成snake_case return self._camel_to_snake(data) @post_dump def handle_outgoing_data(self, data, **kwargs): # 返回给前端时,把snake_case转成camelCase return self._snake_to_camel(data)
步骤2:让业务Schema继承BaseSchema
你的SQLAlchemy模型对应的Schema只需要继承这个BaseSchema,再结合SQLAlchemyAutoSchema就能自动完成转换:
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema from your_app import db from models import User # 假设你的SQLAlchemy模型是User,字段都是snake_case class UserSchema(BaseSchema, SQLAlchemyAutoSchema): class Meta: model = User load_instance = True # 允许把转换后的snake_case数据直接转成模型实例 sqla_session = db.session
步骤3:在API路由中使用Schema
这样在接收前端Angular传来的JSON时,Schema会自动把userName转成user_name,匹配你的SQLAlchemy模型字段;返回响应时,又会把user_name转回userName给前端:
from flask import request, jsonify from schemas import UserSchema user_schema = UserSchema() users_schema = UserSchema(many=True) @app.route('/api/users', methods=['POST']) def create_user(): # 前端发来的是camelCase的JSON,比如{"userName": "Alice", "emailAddress": "alice@example.com"} user_data = user_schema.load(request.get_json()) # user_data已经是转成snake_case的User实例:user_data.user_name, user_data.email_address db.session.add(user_data) db.session.commit() # 返回时自动转成camelCase return jsonify(user_schema.dump(user_data)), 201
备用方案:全局请求响应钩子
如果你需要对所有API路由做统一转换,也可以用Flask的全局钩子,但要注意排除非API路由(比如静态文件):
from flask import request, jsonify def camel_to_snake(data): if isinstance(data, dict): return {''.join(['_' + c.lower() if c.isupper() else c for c in key]).lstrip('_'): camel_to_snake(value) for key, value in data.items()} elif isinstance(data, list): return [camel_to_snake(item) for item in data] else: return data def snake_to_camel(data): if isinstance(data, dict): return {''.join([word.capitalize() if i > 0 else word for i, word in enumerate(key.split('_'))]): snake_to_camel(value) for key, value in data.items()} elif isinstance(data, list): return [snake_to_camel(item) for item in data] else: return data @app.before_request def preprocess_incoming_data(): # 只处理API前缀的路由 if request.path.startswith('/api/') and request.is_json: request.json = camel_to_snake(request.get_json()) @app.after_request def postprocess_outgoing_data(response): # 只处理API前缀的路由 if request.path.startswith('/api/') and response.is_json: response_data = response.get_json() response.set_data(json.dumps(snake_to_camel(response_data))) return response
方案对比
- Marshmallow Schema方案:更推荐!精准控制转换范围,和现有序列化逻辑深度整合,不会影响非API业务,嵌套结构处理更可靠。
- 全局钩子方案:适合需要全API统一转换的场景,但要注意排除不需要转换的路由,避免意外问题。
内容的提问来源于stack exchange,提问作者Yoh0xFF
相关产品推荐
相关产品推荐

