# 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是不考虑业务逻辑的


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://flask-api.gitbook.io/api/flask-gou-jian-ke-kuo-zhan-de-restfulapi6-mo-xing-dui-xiang-de-xu-lie-hua.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
