You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从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)或值。我有三个疑问:

  1. 是否可修改GenderEnum类,让GenderEnum.l返回其名称或值?
  2. 查询数据库时,如何获取GenderEnum的名称或值?
  3. 能否让查询默认返回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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 05:45:30