头图

Sqlalchemy 数据模型序列化(转JSON)

问题

Sqlalchemy 可以很方便地做ORMapping,把数据库记录映射为业务实体类的实例,例如下面这样:

class Student(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(100))
    city = db.Column(db.String(50))
    addr = db.Column(db.String(200))
    pin = db.Column(db.String(10))


if __name__ == '__main__':
    student = Student.query.get(1)
    

但这种实例不方便序列化,比如要想在 Flask Web Service 接口中返回这个对象:

@app.route('/demo/db', methods=['GET'])
def demo_db():
    student = Student.query.get(1)
    return jsonify(student)

或者是直接转 JSON 字符串:

import json

json.dumps(student)

都会报错:Object of type Student is not JSON serializable

方法

在网上搜的话,会有五花八门的答案,大部分都是让你实现某个类似 to_json 的方法,有些根本不管用,有些很麻烦。
其实最简单的解决方案就是:

dataclass

用 python 3.7 引入的 dataclass 装饰器,这个装饰器帮我们实现了对应的方法,可以直接把类变量声明当做实例变量的定义:

from dataclasses import dataclass


@dataclass
class Student(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(100))
    city = db.Column(db.String(50))
    addr = db.Column(db.String(200))
    pin = db.Column(db.String(10))

这时候,再调用 jsonify(student)就不会报错了,但奇怪的是,结果是个空对象:{}
原来 dataclass 装饰器“认可”的字段声明,必须有 type annotation,改成下面这样:

from dataclasses import dataclass


@dataclass
class Student(db.Model):
    id: int = db.Column(db.Integer, primary_key=True)
    name: str = db.Column(db.String(100))
    city: str = db.Column(db.String(50))
    addr: str = db.Column(db.String(200))
    pin: str = db.Column(db.String(10))

输出结果就完美了:

{
  addr: "xxx",
  city: "bj",
  id: 1,
  name: "hui",
  pin: "xyz"
}

不过jsonify虽然可用了,json.dumps依然会报错,最简单的办法,是先把对象转为 dict,再转 json:

import json
from dataclasses import dataclass

print(json.dumps(dataclasses.asdict(student)))

objtyping

在实际使用中,发现 dataclass 还是有局限的:它依赖直接的类 attributes 定义,并且不能获取父类的中的定义,因此像下面的代码:

from dataclasses import dataclass


@dataclass
class BaseObj(db.Model):
    id: int = db.Column(db.Integer, primary_key=True)


@dataclass
class Student(BaseObj):
    name: str = db.Column(db.String(100))
    city: str = db.Column(db.String(50))
    addr: str = db.Column(db.String(200))
    pin: str = db.Column(db.String(10))


if __name__ == '__main__':
    student = Student.query.get(1)
    print(json.dumps(dataclasses.asdict(student)))

就会发现输出结果中,缺少了id字段。

不引入三方库的话,很难通过简洁的代码实现了,这里推荐一个小库:objtyping,对任意实例对象(不需要是dataclass,也无需任何其他装饰),都可以转换为基础类型的dict、list,或者 dict-list 嵌套结构,支持多级嵌套。

首先安装依赖:pip install objtyping

于是上面的Student对象,就可以这样转换:

from objtyping import to_primitive

student = Student.query.get(1)
print(json.dumps(to_primitive(student)))

可以看到,输出结果中,包含id字段了。

结论

一句话总结:如果是python 3.7 以上环境,不在意添加 dataclass 装饰器,并且数据对象实例之间没有继承关系,就用 dataclass 方法;如果数据对象定义包含继承关系,或者是老系统,不想一个个添加,就用 objtyping 三方库。


hawk
关注创业公司的技术与团队
289 声望
20 粉丝
0 条评论
推荐阅读
为什么 AI 时代,人人都需要学一点编程
经常有人调侃说,ChatGPT 来了,你们这些码农和程序员都要失业了,这当然首先是在吸引眼球,有流量才有钱么。这些做自媒体的up主是不会认真想想,AI 到底对编程造成了怎样的冲击,对码农们的影响到底是是什么?真...

songofhawk阅读 581

封面图
花了几个月时间把 MySQL 重新巩固了一遍,梳理了一篇几万字 “超硬核” 的保姆式学习教程!(持续更新中~)
MySQL 是最流行的关系型数据库管理系统,在 WEB 应用方面 MySQL 是最好的 RDBMS(Relational Database Management System:关系数据库管理系统)应用软件之一。

民工哥14阅读 2k

封面图
硬卷完了!MongoDB 打怪升级进阶成神之路( 2023 最新版 )!
前面我们学习:MySQL 打怪升级进阶成神之路、Redis 打怪升级进阶成神之路,然后我们还在继续 NoSQL 的卷王之路。从第一篇文章开始,我们逐步详细介绍了 MogoDB 基础概念、安装和最基本的CURD操作、索引和聚合、工...

民工哥7阅读 704

封面图
基于Sanic的微服务基础架构
使用python做web开发面临的一个最大的问题就是性能,在解决C10K问题上显的有点吃力。有些异步框架Tornado、Twisted、Gevent 等就是为了解决性能问题。这些框架在性能上有些提升,但是也出现了各种古怪的问题难以...

jysong6阅读 4k评论 3

又一款眼前一亮的Linux终端工具!
今天给大家介绍一款最近发现的功能十分强大,颜值非常高的一款终端工具。这个神器我是在其他公众号文章上看到的,但他们都没把它的强大之处介绍明白,所以我自己体验一波后,再向大家分享自己的体验。

良许6阅读 1.9k

初学后端,如何做好表结构设计?
这篇文章介绍了设计数据库表结构应该考虑的4个方面,还有优雅设计的6个原则,举了一个例子分享了我的设计思路,为了提高性能我们也要从多方面考虑缓存问题。

王中阳Go4阅读 1.8k评论 2

封面图
又一款内存数据库横空出世,比 Redis 更强,性能直接飙升一倍!杀疯了
KeyDB是Redis的高性能分支,专注于多线程,内存效率和高吞吐量。除了多线程之外,KeyDB还具有仅在Redis Enterprise中可用的功能,例如Active Replication,FLASH存储支持以及一些根本不可用的功能,例如直接备份...

民工哥4阅读 1.7k评论 2

封面图
289 声望
20 粉丝
宣传栏