django之序列化

序列化的概念

去网上检索序列化的概念,大概会得到如下的答案。

序列化 :将数据结构或对象状态 转换为一种可以存储或传输的格式的过程。这个过程使数据能够在不同的系统、平台或语言之间进行交换。

反序列化 :是相反的过程:将序列化后的数据还原为原始的数据结构或对象。

还是不太好理解,简单说下我的理解。

序列化就是为了数据交换做的一层翻译。

就比如键值对(key-value) 这个数据结构,在python中是字典,在javascript中是对象,在java中是hashmap,在数据库中是关系表。

如果一个python系统A想要把自己的键值对保存到数据库,或者告诉java系统B,因为语言不通,则系统A在传输之前,需要先将数据转化成一种接收方可以理解的格式。这个转化过程就是序列化。

常用的序列化的结果格式有JSON、XML等。

反序列化就是接收方把JSON、XML等转为自己支持的数据结构或对象。

在DRF中,序列化器是同时支持序列化和反序列化,因此序列化的代码和反序列化的代码也是揉在一起的,分析起来就不太容易理清。因此下面的分析采用面向功能进行拆解,把序列化和非序列化分开解析。

另外也有些代码是强合在一起,两边都会用的。比如初始化函数

python 复制代码
class BaseSerializer(Field):
    def __init__(self, instance=None, data=empty, **kwargs):
        pass

当序列化时,只需要传入instance参数,传入一个object或者queryset实例即可。

当反序列化时,如果是新增数据,只需要将原始数据传入data,不需要传instance。

如果是要更新数据,除了要将更新数据传入data,还要将要更新的object传入instance。

初始化时的主要参数有:

data: 需要反序列化的数据,一般取自视图函数的request.data

instance: 序列化或者修改数据的时候使用。

many: bool类型,模型实例为查询结果集(QuerySet)需要传入

partial:部分更新模型实例数据的时候需要用到

context:需要传入额外反序列化数据的时候用到,如传入user_id

DRF中的序列化

序列化基本使用方法

python 复制代码
# 定义model
from django.db import models

class User(models.Model):
   name = models.CharField(max_length=12, verbose_name='姓名')
   birthday = models.DateField(verbose_name='出生年月')

   class Meta:
        verbose_name = '用户表'

# 定义序列化:serializers.Serializer
from rest_framework import serializers

class UserSerializer(serializers.Serializer):
   name = serializers.CharField(max_length=12,)
   birthday = serializers.DateField()

 # 视图中使用,序列化
from rest_framework.views import APIView

class UserViewSet(APIView):

    # 对单个用户信息序列化
    def get(self,request,pk,*args,**kwargs):
        user_obj = User.objects.get(id=pk)
        # instance传入单个object
        ser = UserSerializer(instance=user_obj)
        return Response(data=ser.data)

    # 对多个用户序列化
    def get(self,request,*args,**kwargs):
        user_qs = User.objects.all()
        # instance传入queryset时,设置many=True
        ser = UserSerializer(instance=user_qs, many=True)
        return Response(data=ser.data)



# 上面使用serializers.Serializer需要把models中的每个字段再定义一遍,比较麻烦
# 因此DRF提供了更便捷的方法serializers.ModelSerializer
# ModelSerializer会自动拆解model中的字段
class UserSerializer(serializers.ModelSerializer):

    class Meta:
        model = User
        fields = '__all__'

基础流程代码解析

总结下基础使用样例中对序列化的使用:

1、序列化定义

class UserSerializer(serializers.Serializer):

2、初始化

ser = UserSerializer(instance=user_obj)

或者

ser = UserSerializer(instance=user_qs, many=True)

3、获取序列化后的数据

ser.data

先看下初始化的过程ser = UserSerializer(instance=user_obj)

python 复制代码
# rest_fromework.serializers.py
# class Serializer(BaseSerializer)类并没有定义__new__、__init__方法,因此走BaseSerializer类的初始化

class BaseSerializer(Field):

    def __new__(cls, *args, **kwargs):
        # 当many=True时,使用many_init函数新建实例,最终的序列化器使用ListSerializer
        if kwargs.pop('many', False):
            return cls.many_init(*args, **kwargs)
        # 当many=False时,使用当前类新建实例,最终的序列化器使用Serializer
        return super(BaseSerializer, cls).__new__(cls, *args, **kwargs)

    @classmethod
    def many_init(cls, *args, **kwargs):
        # 此处的child_serializer就是用户自定义的序列化器,比如UserSerializer
        # 注意__new__函数中的kwargs.pop('many', False)
        # 因此此处child_serializer是不带many参数的序列化器
        # 也就是此处的UserSerializer的父类是Serializer而不是ListSerializer
        child_serializer = cls(*args, **kwargs)
        list_kwargs = {
            'child': child_serializer,
        }
        # 此处支持用户自定义List序列化方法
        meta = getattr(cls, 'Meta', None)
        # 默认使用ListSerializer类
        list_serializer_class = getattr(meta, 'list_serializer_class', ListSerializer)
        return list_serializer_class(*args, **list_kwargs)

也就是在many=True的情况下UserSerializer是serializers.ListSerializer的子类,

在many=False的情况下UserSerializer是serializers.Serializer的子类。

先分析下ser = UserSerializer(instance=user_obj)的情况,也就是

python 复制代码
# rest_fromework.serializers.py

# 先从Serializer类入手,获取该类的data
@six.add_metaclass(SerializerMetaclass)
class Serializer(BaseSerializer):
   @property
    def data(self)
:       # 调用父类的data
        ret = super(Serializer, self).data
        # ReturnDict就是一个有序字典,只不过增加了一个属性指向序列化器
        return ReturnDict(ret, serializer=self)


class BaseSerializer(Field):
    @property
    def data(self):
        if not hasattr(self, '_data'):
            # instance is not None是序列化过程中使用的,其他分支是反序列化使用的
            if self.instance is not None and not getattr(self, '_errors', None):
                # 调用实例的to_representation
                self._data = self.to_representation(self.instance)
        return self._data


class Serializer(BaseSerializer):
    def to_representation(self, instance):
        ret = OrderedDict()
        fields = self._readable_fields
        # 循环所有可读字段
        for field in fields:
            attribute = field.get_attribute(instance)
            # 调用每个字段的to_representation,对每个字段进行序列化
            ret[field.field_name] = field.to_representation(attribute)
        return ret

再看下ser = UserSerializer(instance=user_qs, many=True)的情况

python 复制代码
class ListSerializer(BaseSerializer):
    def __init__(self, *args, **kwargs):
        # 此处child是用户自定义的序列化类UserSerializer
        self.child = kwargs.pop('child', copy.deepcopy(self.child))
        super(ListSerializer, self).__init__(*args, **kwargs)

    # .data调用此处的to_representation方法
    def to_representation(self, data):
        iterable = data.all() if isinstance(data, models.Manager) else data
        # 返回用户自定义序列化器的to_representation方法
        # 也就是Serializer类的to_representation
        return [
            self.child.to_representation(item) for item in iterable
        ]

class Serializer(BaseSerializer):
    def to_representation(self, instance):
        ret = OrderedDict()
        fields = self._readable_fields
        # 循环所有可读字段
        for field in fields:
            attribute = field.get_attribute(instance)
            # 调用每个字段的to_representation,对每个字段进行序列化
            ret[field.field_name] = field.to_representation(attribute)
        return ret

source参数

source参数的作用有

1、修改后端真实字段名

2、数据由表模型的方法生成,表中可能不存在相关字段。

3、关系型字段跨表操作。

用法如下:

python 复制代码
# 表中存储的是name字段,序列化出来的是zh_name
class UserSerializer(serializers.Serializer):
   zh_name = serializers.CharField(source="name")

# souce指向函数
class User(models.Model):
    birthday = models.DateField(verbose_name='出生年月')

    def age(self):
        # 伪代码
        return date.today() - self.birthday 

class UserSerializer(serializers.Serializer):
   age = serializers.IntegerField(source="age", read_only=True)

# souce指向关联字段,以点号连接
class User(models.Model):
    dept= models.ForeignKey(to="deptment",verbose_name='所属部门',db_constraint=False)

class UserSerializer(serializers.Serializer):
    dept_name = serializers.CharField(source="dept.name", read_only=True)

相关代码说明

python 复制代码
# 获取单个字段的数据实例位于to_representation函数
# attribute = field.get_attribute(instance)

# rest_framework.fields
class Field(object):
    # 解析source属性,转化为内部使用的self.source_attrs
    def bind(self, field_name, parent):
         if self.source is None:
            self.source = field_name
         # SerializerMethodField字段就会用到source='*'
         if self.source == '*':
            self.source_attrs = []
        else:
            # 拆分多级字段
            self.source_attrs = self.source.split('.')

    def get_attribute(self, instance):
        return get_attribute(instance, self.source_attrs)


def get_attribute(instance, attrs):
    # 对于嵌套多层,逐层获取字段实例
    for attr in attrs:
        try:
            if isinstance(instance, collections.Mapping):
                instance = instance[attr]
            else:
                instance = getattr(instance, attr)
    # 对于函数,直接调用函数返回实例
    if is_simple_callable(instance):
        instance = instance()
    return instance

SerializerMethodField的使用

上面聊的source属性可以调用模型中的方法,序列化器也提供了可替代的机制,就是SerializerMethodField。

python 复制代码
# model里只存储出生年月
class User(models.Model):
    birthday = models.DateField(verbose_name='出生年月')

class UserSerializer(serializers.Serializer):
   age = serializers.SerializerMethodField(read_only=True)

   def get_age(self, obj):
       # 伪代码
       return date.today() - obj.birthday

可以通过SerializerMethodField定义一个model里不存在的字段,然后定义一个名为get_{字段名}的函数,即可实现自定义的取数逻辑。

python 复制代码
# rest_framework.fields
class SerializerMethodField(Field):

    def bind(self, field_name, parent):
        # 默认调用函数名为get_{field_name}
        default_method_name = 'get_{field_name}'.format(field_name=field_name)
        # 也可以通过method_name属性直接指定函数名
        if self.method_name is None:
            self.method_name = default_method_name

        super(SerializerMethodField, self).bind(field_name, parent)

    def to_representation(self, value):
        method = getattr(self.parent, self.method_name)
        # 执行函数,获取数据
        return method(value)

嵌套序列化器

序列化器可以嵌套使用。

python 复制代码
class DeptSerializer(serializers.Serializer):
    dept_name = serializers.CharField(max_length=12)

class UserSerializer(serializers.Serializer):
    dept = DeptSerializer()

相关代码

python 复制代码
class BaseSerializer(Field):
    pass
# 序列化器集成了Field类,也就是说序列化器也是field的一种。
# 因此就可以直接嵌套解析了

depth参数

对于使用serializers.ModelSerializer的情况,默认是将关系自动解析成id,也就是PrimaryKeyRelatedField。

如果想要自动实现关系字段展开,就可以使用depth参数

python 复制代码
class User(models.Model):
    dept= models.ForeignKey(to="deptment",verbose_name='所属部门',db_constraint=False)

class UserSerializer(serializers.Serializer):
    class Meta:
        model = User
        fields = '__all__'
        depth = 1

相关代码:

python 复制代码
class ModelSerializer(Serializer):
    def get_fields(self):
        # 不设置depth的情况下,默认值为0
        depth = getattr(self.Meta, 'depth', 0)
        for field_name in field_names:
            # ModelSerializer需要自动展开models中的字段,build_field对每个字段进行展开处理
            field_class, field_kwargs = self.build_field(
                source, info, model, depth
            )
        return fields

    def build_field(self, field_name, info, model_class, nested_depth):
        # 非关系字段,使用build_standard_field构建
        if field_name in info.fields_and_pk:
            model_field = info.fields_and_pk[field_name]
            return self.build_standard_field(field_name, model_field)
        # 关系字段处理
        elif field_name in info.relations:
            relation_info = info.relations[field_name]
            # depth=0时,使用build_relational_field处理,转为主键格式
            if not nested_depth:
                return self.build_relational_field(field_name, relation_info)
            # depth!=0时,使用build_nested_field处理成嵌套格式
            else:
                return self.build_nested_field(field_name, relation_info, nested_depth)

DRF中的反序列化

常规的反序列化就是把A格式转为B格式,但DRF中的反序列化不能按照常规概念去理解。

转格式只是DRF反序列化提供的一个环节,之所以需要把前端数据转成后端的model对象格式,主要是为了保存或者更新后端数据表(model),因此序列化器默认提供了create()、update()方法。

因为要持久化数据,数据保存之前要加一层校验,比如常规的字段长度、是否为空等,于是序列化器又提供了数据校验的工具方法is_valid()函数。

而核心的反序列化的过程,又被打包进is_valid的过程中。

并且在create、update之前也必须要调用is_valid。因为create、update使用的数据validated_data是由is_valid函数生产的。因此可以说,is_valid函数处于整个反序列化过程的入口位置。

所以整体看,DRF的反序列化的功能如下:
validated_data
validated_data
数据校验+格式转化is_valid
保存model save
更新model update

以下分析主要基于serializers.ModelSerializer,因为ModelSerializer实现了update、create方法,一般情况下不需要自定义就可以用,而serializers.Serializer需要自己实现update、create方法。
基础使用

python 复制代码
from rest_framework.views import APIView

class UserViewSet(APIView):
    # 新增数据
    def post(self, request, *args, **kwargs):
        ser = UserSerializer(data=request.data)
        ser.is_valid(raise_exception=True)
        ser.save()
        return Response(ser.data)

   # 部分更新数据
   def patch(self, request, pk, *args, **kwargs):
       instance = User.objects.get(id=pk)
       ser = UserSerializer(instance=instance, data=request.data, partial=True)
       ser.is_valid(raise_exception=True)
       ser.save()
       return Response(ser.data)

   # 不更新不保存,只对数据进行校验,很少有场景会用到
   def put(self, request, pk, *args, **kwargs):
       ser = UserSerializer(data=request.data)
       ser.is_valid(raise_exception=True)
       # 对校验过后的数据进行自定义处理,compute_user_info是自定义函数
       compute_user_info(ser.validated_data)
       return Response(ser.data)

数据校验+格式转化is_valid

增加数据验证有以下几种方式:

1、字段级别定义validators属性,适合定义可复用的字段验证逻辑。

2、序列化器中定义validate_{字段名}的验证函数,适合对单个字段验证。

3、序列化器增加validate函数,适合跨字段关联验证。

python 复制代码
from rest_framework import serializers
from rest_framework.validators import ValidationError

# 自定义验证化器,参数为验证的值value,若验证不通过则raise serializers.ValidationError
# 验证通过返回验证通过的value即可
class UniqueUsernameValidator:
    def __call__(self, value):
        if User.objects.filter(username=value).exists():
            raise serializers.ValidationError("用户名已存在")
        return value

class UserSerializer(serializers.Serializer):
    # 字段级别定义validators属性,
    username = serializers.CharField(
        validators=[UniqueUsernameValidator()]
    )

    # 字段级别验证,定义validate_{字段名}
    def validate_username(self, value):
        if 'admin' in value.lower():
            raise ValidationError("不能包含敏感词")
        return value

    # 对象级别验证,validate函数,可支持跨字段
    # attrs为包含所有字段的字典
    def validate(self, attrs):
        if attrs['password'] != attrs['confirm_password']:
            raise serializers.ValidationError({
                'confirm_password': '两次密码不一致'
            })
        return attrs

代码解析先从入口函数is_valid开始。

python 复制代码
# ModelSerializer类、Serializer类都没有重写is_valid,使用的还是BaseSerializer类中的。
class ModelSerializer(Serializer):
    pass

@six.add_metaclass(SerializerMetaclass)
class Serializer(BaseSerializer):
    pass

class BaseSerializer(Field):
    def is_valid(self, raise_exception=False):
        if not hasattr(self, '_validated_data'):
            try:
                # 调用序列化器的run_validation进行验证
                self._validated_data = self.run_validation(self.initial_data)

class Serializer(BaseSerializer):

    def run_validation(self, data=empty):
        # 先调用序列化类的to_internal_value函数--字段级别验证 + 反序列化
        value = self.to_internal_value(data)
        try:
            # 再调用序列化类的run_validators函数
            self.run_validators(value)
            # 最后调用序列化类validate函数--全局验证函数
            value = self.validate(value)
        return value

    # 序列化器的to_internal_value,实现字段级别验证 + 反序列化
    def to_internal_value(self, data):
        ret = OrderedDict()
        fields = self._writable_fields

        for field in fields:
            validate_method = getattr(self, 'validate_' + field.field_name, None)
            primitive_value = field.get_value(data)
            try:
                # 字段级别的run_validation
                validated_value = field.run_validation(primitive_value)
                # 字段级别的validate_{字段名}函数
                if validate_method is not None:
                    validated_value = validate_method(validated_value)
            else:
                # 反序列化完成赋值给字典
                set_value(ret, field.source_attrs, validated_value)
       return ret

   def run_validators(self, value):
        # 精简掉去掉默认值的部分
        to_validate = value
        # 调用的Field类的run_validators
        super(Serializer, self).run_validators(to_validate)

    # 全局验证函数,支持跨字段验证
   def validate(self, attrs):
        return attrs


 class Field(object):

     # 字段级别的处理
     def run_validation(self, data=empty):
        # 对单个字段反序列化
        value = self.to_internal_value(data)
        # 运行字段级别定义的验证器
        self.run_validators(value)
        return value

    # 单个字段的反序列化函数,每个字段中自定义的
    def to_internal_value(self, data):
        raise NotImplementedError(
            '{cls}.to_internal_value() must be implemented.'.format(
                cls=self.__class__.__name__
            )
        )
    # 字段定义的验证器
    def run_validators(self, value):
        # 其中validators是字段属性,通过字段初始化的时候传入
        for validator in self.validators:
            try:
                validator(value)

    # 具体字段级别validators属性的逻辑
    @property
    def validators(self):
        if not hasattr(self, '_validators'):
            self._validators = self.get_validators()
        return self._validators

    @validators.setter
    def validators(self, validators):
        self._validators = validators

    def __init__(self, read_only=False, write_only=False,
                 required=None, default=empty, initial=empty, source=None,
                 label=None, help_text=None, style=None,
                 error_messages=None, validators=None, allow_null=False):
         pass

数据保存

主要涉及三个函数

save()

create()

update()

但从上面使用可以知道,不管是更新还是保存,都是调用ser.save方法,而没有直接调用create、update方法,因为save方法会根据传参进行判定,是要update还是create。

python 复制代码
class BaseSerializer(Field):
    # 调用save之前必须要调用is_valid的原因在这,用到了self.validated_data
    validated_data = dict(
            list(self.validated_data.items()) +
            list(kwargs.items())
        )
    # 反序列化过程中,对instance赋值就是更新,否则就是新增
    # 序列化过程中,也是对instance赋值,参数复用,容易误导
    if self.instance is not None:
        self.instance = self.update(self.instance, validated_data)
    else:
        self.instance = self.create(validated_data)
    return self.instance

# 只有ModelSerializer实现了create、update,使用其他序列化类要自定义
# create、update方法时,validated_data参数是直接传入的,并没有使用self.validated_data.
# 因此这两个方法并不直接依赖is_valid
# 只是为了数据安全,强烈建议先调用is_valid函数后使用self.validated_data传入create/update
class ModelSerializer(Serializer):
    def create(self, validated_data):
        pass

    def update(self, instance, validated_data):
        pass

ModelSerializer反序列化时对关系字段的支持说明:

ModelSerializer默认的create和update方法支持关系字段,但有以下特点:
支持类型:

支持ForeignKey、OneToOneField(使用主键)

支持ManyToManyField(使用主键列表)
默认行为:

默认都使用主键字段进行操作,即前端传给后端的需要是主键,而不是对象。
限制:

不支持嵌套序列化器的创建/更新(需要自定义)

不支持反向关系字段(除非显式定义)

不支持复杂的关联对象创建逻辑

所以如果遇到限制情况,还是需要手动重写create\update

Django中的序列化

Django中序列化并没有向DRF中那么重的角色。

因为DRF是前后端分离的框架,需要后端把序列化后的数据传给前端,

而Django默认是返回给浏览器html,在view中可以直接把对象传递给Template系统,Template系统支持对象解析,并渲染成html后返给浏览器,因此很多时候用不到序列化。

但Django还是提供了序列化的方法。

基础使用

python 复制代码
from django.core.serializers import serialize
from django.http import HttpResponse
from .models import Student

class UserView(View):
    def get(self, request):
        # 获取查询集
        origin_students = User.objects.all()
        # 将查询集序列化为JSON格式
        serialized_students = serialize("json", origin_students)
        # 将JSON数据响应给客户端
        return HttpResponse(serialized_students, content_type='application/json')

Django中的序列化方法就跟常规的序列化概念很像,

提供一个工具函数,传入待转化的对象A和格式要求,输出转化后的数据。

其中格式支持json、xml、yaml等。待转化的对象为QuerySet。

代码结构比较简单,就不再展开说明了,直接贴出来工具函数的定义如下。

python 复制代码
# django.core.serializers.__init__

def serialize(format, queryset, **options):
    s = get_serializer(format)()
    s.serialize(queryset, **options)
    return s.getvalue()

另外还有一个函数也可以支持对象转字典--model_to_dict。

python 复制代码
class UserView(View):
    def get(self, request, *args, **kwargs):
        obj = User.objects.get(id=pk)
        obj_dict = model_to_dict(obj)
        return HttpResponse(obj_dict , content_type='application/json')

相关代码解析

python 复制代码
# django.forms.models
def model_to_dict(instance, fields=None, exclude=None):
    opts = instance._meta
    data = {}
    for f in chain(opts.concrete_fields, opts.private_fields, opts.many_to_many):
        if not getattr(f, 'editable', False):
            continue
        if fields and f.name not in fields:
            continue
        if exclude and f.name in exclude:
            continue
        data[f.name] = f.value_from_object(instance)
    return data

Django中的反序列化

Django中的反序列化其实主要在forms模块中实现,包括数据校验,因为现在大家都有DRF了,这部分就不展开说明了。

相关推荐
尘觉2 小时前
创作 1024 天|把热爱写成长期主义
数据库·1024程序员节
summerkissyou19872 小时前
Android13-蓝牙-发现,配对,连接-例子
android·蓝牙
游戏开发爱好者82 小时前
iOS App 抓不到包时的常见成因与判断思路,结合iOS 调试经验
android·ios·小程序·https·uni-app·iphone·webview
林多3 小时前
【Android】SU命令源码解析
android·源码·命令·cat·su
云边有个稻草人3 小时前
KingbaseES 数据库赋能:时序数据库国产化替代的硬实力范本
数据库·时序数据库·国产数据库·金仓·kingbasees sql
今晚务必早点睡10 小时前
微服务改数据库密码后服务仍能访问?一次“看似异常、实则常见”的生产现象全解析
数据库·微服务·oracle
老师我太想进步了202612 小时前
cmd连接MySQL及相关查询
数据库·mysql
低调小一13 小时前
深度复盘:KMP 在字节跳动的工程化落地实践
android·kotlin
難釋懷14 小时前
Redis命令-Set命令
数据库·redis·缓存