如何从SQLAlchemy查询结果中获取枚举的名称或值?
解决Flask+SQLAlchemy枚举字段JSON序列化问题
问题背景
我用Flask和SQLAlchemy开发API,要求所有数据以JSON格式返回。之前用数据类解决了SQLAlchemy查询结果无法JSON序列化的问题,但遇到gender这类枚举字段时,枚举对象又导致序列化失败。相关代码如下:
ActivityLevel.py
class GenderEnum(Enum): p = 0 l = 1 @dataclass class ActivityLevel(db.Model): __tablename__ = "activity_level" id: int name: str gender: GenderEnum activity_score: float date_created: str id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100), nullable=False) gender = db.Column(db.Enum(GenderEnum), nullable=False) activity_score = db.Column( db.Float(precision=3, decimal_return_scale=2), nullable=False ) date_created = db.Column(db.DateTime, default=datetime.utcnow)
ActivityLevelController.py
from flask import jsonify from flask_restful import Resource from models.ActivityLevel import ActivityLevel class ActivityLevelController(Resource): def get(self): try: activity = ActivityLevel().query.all() result = { "activity": activity } print(activity) return jsonify(result) except Exception as e: print(e) return jsonify({"message": "Error again"})
print(activity)输出结果:
[ ActivityLevel(id=1, name='asdfasdf', gender=<GenderEnum.p: 0>, activity_score=12.0, date_created=datetime.datetime(2022, 8, 12, 10, 54, 58)), ActivityLevel(id=2, name='qwerqwer', gender=<GenderEnum.l: 1>, activity_score=13.0, date_created=datetime.datetime(2022, 8, 12, 10, 54, 58)) ]
可见gender字段返回的是枚举对象,而非其名称(如l、p)或值。我有三个疑问:
- 是否可修改GenderEnum类,让
GenderEnum.l返回其名称或值? - 查询数据库时,如何获取GenderEnum的名称或值?
- 能否让查询默认返回gender的名称或值而非枚举对象?
解决方案
1. 修改GenderEnum类,让枚举实例返回名称或值
可以通过重写枚举类的__str__或__repr__方法,改变枚举对象的字符串表现:
from enum import Enum class GenderEnum(Enum): p = 0 l = 1 def __str__(self): # 返回枚举名称(p/l),要返回值就改成return str(self.value) return self.name def __repr__(self): return self.name
重写后,打印枚举对象时会直接显示名称,但SQLAlchemy底层仍会以枚举对象存储,只是字符串输出变了。
2. 查询时获取GenderEnum的名称或值
有两种常用方式:
方式一:手动遍历转换结果
在控制器中处理查询到的对象,将枚举字段转成名称或值:
def get(self): try: # 注意:直接用类的query属性,不要实例化后再查 activities = ActivityLevel.query.all() result_list = [] for activity in activities: activity_dict = { "id": activity.id, "name": activity.name, "gender": activity.gender.name, # 取枚举名称,要值用activity.gender.value "activity_score": activity.activity_score, "date_created": activity.date_created.strftime("%Y-%m-%d %H:%M:%S") } result_list.append(activity_dict) return jsonify({"activity": result_list}) except Exception as e: print(e) return jsonify({"message": "Error again"})
方式二:SQLAlchemy查询时直接提取
用查询表达式直接获取枚举的名称/值:
from sqlalchemy import func def get(self): try: activities = db.session.query( ActivityLevel.id, ActivityLevel.name, ActivityLevel.gender.name.label("gender"), ActivityLevel.activity_score, ActivityLevel.date_created ).all() # 转换为字典列表 result_list = [dict(row._mapping) for row in activities] # 处理datetime序列化 for item in result_list: item["date_created"] = item["date_created"].strftime("%Y-%m-%d %H:%M:%S") return jsonify({"activity": result_list}) except Exception as e: print(e) return jsonify({"message": "Error again"})
3. 让查询默认返回gender的名称/值而非枚举对象
可以通过三种方式实现自动转换:
方式一:模型中用property封装字段
在ActivityLevel类中用property把枚举字段转成名称/值:
@dataclass class ActivityLevel(db.Model): __tablename__ = "activity_level" id: int name: str gender: str activity_score: float date_created: str id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100), nullable=False) # 数据库列名改为_gender,避免和property冲突 _gender = db.Column(db.Enum(GenderEnum), nullable=False) activity_score = db.Column(db.Float(precision=3, decimal_return_scale=2), nullable=False) date_created = db.Column(db.DateTime, default=datetime.utcnow) @property def gender(self): # 返回枚举名称,要值用self._gender.value return self._gender.name @gender.setter def gender(self, value): # 支持传入枚举对象、名称或值 if isinstance(value, GenderEnum): self._gender = value elif value in ["p", "l"]: self._gender = GenderEnum[value] elif value in [0, 1]: self._gender = GenderEnum(value) else: raise ValueError("Invalid gender value")
方式二:自定义SQLAlchemy枚举类型
用TypeDecorator自定义枚举类型,自动处理查询和写入的转换:
from sqlalchemy import TypeDecorator, Enum class GenderType(TypeDecorator): impl = Enum(GenderEnum) def process_result_value(self, value, dialect): # 查询结果返回枚举名称 return value.name if value is not None else None def process_bind_param(self, value, dialect): # 写入数据库时转成枚举对象 if isinstance(value, str): return GenderEnum[value] elif isinstance(value, int): return GenderEnum(value) return value # 模型中使用自定义类型 gender = db.Column(GenderType, nullable=False)
查询时activity.gender会直接返回枚举名称,写入时支持传入名称、值或枚举对象。
方式三:自定义Flask JSON编码器
扩展Flask的JSON编码器,自动处理枚举和datetime的序列化:
from flask import Flask from enum import Enum from datetime import datetime app = Flask(__name__) class CustomJSONEncoder(app.json_encoder): def default(self, obj): if isinstance(obj, Enum): # 返回枚举名称,要值用obj.value return obj.name elif isinstance(obj, datetime): return obj.strftime("%Y-%m-%d %H:%M:%S") return super().default(obj) app.json_encoder = CustomJSONEncoder
之后用jsonify序列化时,会自动把枚举对象转成名称,无需手动处理。
内容的提问来源于stack exchange,提问作者Alif Irhas
相关产品推荐
相关产品推荐

