> For the complete documentation index, see [llms.txt](https://flask-api.gitbook.io/api/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://flask-api.gitbook.io/api/flask-gou-jian-ke-kuo-zhan-de-restfulapi6-mo-xing-dui-xiang-de-xu-lie-hua.md).

# 6. 模型对象的序列化

## 1.理解序列化时的default函数

我们最想做的一件事情，就是在视图函数中，读取出模型之后，还要把他的属性读出来，转换成一个字典。我们想直接`jsonfiy(user)`

现在jsonfiy并不能直接序列化对象，所以我们的目标就是必须想办法让jsonfiy直接序列化对象。

jsonfiy在序列化对象的时候，如果不知道如何序列化当前传进来的参数，就会去调用JSONEncoder类的default函数。

```
def default(self, o):
"""Implement this method in a subclass such that it returns a
serializable object for ``o``, or calls the base implementation (to
raise a :exc:`TypeError`).

For example, to support arbitrary iterators, you could implement
default like this::

def default(self, o):
    try:
        iterable = iter(o)
    except TypeError:
        pass
    else:
        return list(iterable)
    return JSONEncoder.default(self, o)
"""
    if isinstance(o, datetime):
        return http_date(o.utctimetuple())
    if isinstance(o, date):
        return http_date(o.timetuple())
    if isinstance(o, uuid.UUID):
        return str(o)
    if hasattr(o, '__html__'):
        return text_type(o.__html__())
    return _json.JSONEncoder.default(self, o)
```

目前的default是没有提供对对象的序列化的，所以我们这里最关键的就是要重写default方法。在重写的过程中实现对对象的序列化就可以了

## 2.不完美的对象转字典

我们首先要做到的就是让Flask可以调用到我们自己定义的default函数。要做到这一点，我们需要继承JSONEncoder，然后重写defualt方法，然后继承Flask，在子类里，替换掉Flask原有的json*encoder对象。然后，是实例化Flask核心对象的时候，使用我们的子类进行实例化 app\_init*.py

```
from flask import Flask as _Flask

from flask.json import JSONEncoder as _JSONEncoder

class JSONEncoder(_JSONEncoder):
    def default(self,o):
        # return o.__dict__  # 内置方法，将对象转化为字典  # 缺点是只能转换实例变量，不能将类变量也转换成字典
    return dict(o)

class Flask(_Flask):  # 定义自己的Flask核心对象，继承原来的Flask核心对象
    json_encoder = JSONEncoder # 替换原本的JSONEncoder


# 将Blueprint注册到flask核心对象上,并传入一个前缀'/v1'
def register_blueprints(app):
    # from app.api.v1.user import user
    # from app.api.v1.book import book
    # app.register_blueprint(user)
    # app.register_blueprint(book)
    from app.api.v1 import create_blueprint_v1
    app.register_blueprint(create_blueprint_v1(), url_prefix = '/v1')

def registe_plugin(app):  # 插件的注册
    from app.models.base import db
    db.init_app(app)

    with app.app_context():  # 上下文环境 把app推入到上下文栈中 才能使用create_all
        db.create_all()  # 来创建所有数据库，数据表

def create_app():
    app = Flask(__name__)   # 实例化flask核心对象
    app.config.from_object('app.config.secure')  # 读取配置文件下的secure
    app.config.from_object('app.config.setting') # 读取配置文件下的setting

    register_blueprints(app)    # 注册蓝图到核心对象上
    registe_plugin(app)  # 最后调用 registe_plugin

    return app
```

上面的写法o.**dict**只能转换实例变量，不能将类变量也转换成字典。

## 3.深入理解dict机制

在Python中创建一个dict有很多种方式:

1. 直接定义一个字典

   ```
   r = {
   'name': 'gwf'
   }
   ```
2. 使用dict函数

   ```
   r = dict(name='gwf')
   ```
3. 将一个对象传入dict函数 值得研究的是这第三种方法，当将一个对象传入dict函数的时候，他会去调用keys函数

![image](http://upload-images.jianshu.io/upload_images/14597179-b9ec37217f768ff1?imageMogr2/auto-orient/strip|imageView2/2/w/1240)

keys 方法的目的就是为了拿到字典里所有的键，至于说这些键有那么，完全有我们自己来定义。keys 返回的必须是一个元组或者列表来声明要序列化的键。

而dict会以中括号的形式来拿到对应的值，如o\["name"]，但是默认是不能这么访问的，我们需要编写**getitem**函数

```
r = {'name': 'weilai'}  # 直接定义一个字典

r = dict(name= 'weilai') # 使用dict函数

class Wei():
    name = 'weilai'
    age = 'age'

    def __init__(self):
        self.gender = 'male'

    def keys(self):   
        return ('name','age','gender')  #  取到 key ,  做到自定义key 
        # # return ('name',)  # 一个元素的元组
        # return ['name']  # return 序列类型的都可以

    def __getitem__(self,item):  
        return getattr(self,item)   # 取到 key对应的value

o = Wei()
print(dict(o))   # {'name': 'weilai', 'age': 'age', 'gender': 'male'}
```

这样我们就成功的讲一个对象转化成了字典的形式，并且无论类变量和实例变量，都可以转化，更加灵活的是，我们可以自己控制，那些变量需要转化，哪些变量不需要转化

## 4.序列化SQLALChemy模型

有了之前的基础，我们就知道怎么序列化user对象了，我们只需要在User类中定义keys和getitem方法，然后在default函数中使用dict()函数即可

```
class JSONEncoder(_JSONEncoder):
    def default(self, o):
        return dict(o)

class Flask(_Flask):
    json_encoder = JSONEncoder
```

models/user.py

```
class User(Base):
    id = Column(Integer, primary_key=True)
    email = Column(String(50), unique=True, nullable=False)
    auth = Column(SmallInteger, default=1)
    nickname = Column(String(24), nullable=False)
    _password = Column('password', String(128))

# SQLALChemy的实例化是不会调用__init__函数的，要想让他调用就需要
# @orm.reconstructor这个装饰器
    @orm.reconstructor
    def __init__(self):
        self.fields = ['id', 'email', 'nickname']

    def keys(self):
    return self.fields

    # 支持隐藏字段
    def hide(self, *keys):
    [self.fields.remove(key) for key in keys]
    # 支持添加字段
    def append(self, *keys):
    [self.fields.append(key) for key in keys]
```

## 5.完善序列化

优化1：每一个模型如果需要序列化，都要有getitem方法，可以放到基类里面去

优化2：default函数，是递归调用的，只要遇到不能序列化的对象，就会调用default函数。所以如果有其他类型，我们需要修改完善我们的default函数

优化3：我们的default函数需要增加容错性

```
class JSONEncoder(_JSONEncoder):
    def default(self,o):
        # return o.__dict__  # 内置方法，将对象转化为字典  # 缺点是只能转换实例变量，不能将类变量也转换成字典
        # return dict(o) # 考虑不全面.o得有上次定义的那两种方法才不会报错

        if hasattr(o, 'keys') and hasattr(o, '__getitem__'):
            return dict(o)   # 得有这两种方法 才会return dict(o)
        if isinstance(o, date):  # 如果是 时间类型
            return o.strftime('%Y-%m-%d')
        raise ServerError()
```

优化4：之前编写的新的Flask类，JsonEncoder类都是不会轻易改变的，放到app.py中。 一些其他方法，却是 经常改变的，应该把他们放在init文件中

## 6.ViewModel对于API有意义吗？

viewmodel对于API来说，特别是内部开发来说非常有意义

viewmodel是为了我们的视图层，提供个性化的试图模型。SQLALChemy返回的模型是原始模型（格式和数据库中存储的一模一样）。 而前端可能需要我们返回一个意义更加明确的字段。

原始模型是根据数据库来生成的，他的格式是一定的，但是我们在视图层中或者API的返回中，要根据业务去具体的个性化一个个属性的 格式，这就必然存在一个由原始模型向视图模型转换的过程，这个过程最适合的是在View\_model中进行一个转换。

我们在视图层写转换的代码，一来会污染视图层的代码，二来会难以复用 并且有的试图模型可能会比较复杂，设计到多个原始模型，这个代码必定会比较复杂，写在视图函数中就会非常不合适

对于完全严格意义上的RESTFul，viewmodel的意义并不大，因为完全资源意义的RESTFul是不考虑业务逻辑的
