serializer序列化器的源码分析与分析序列化嵌套的问题

serializer序列化器的嵌套组成新的序列器—–遵循适配器模式


一、个人对适配器模式的理解

假如当前有一个接口,比如浏览记录的序列化器,序列化model后形成的json格式的数据为这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[
{
"commodity_name": "旺仔牛奶",
"grade": "四星好评",
"reward_content": "宝贝非常好,质量不错,下次继续买你家的,不过物流太慢了,好几天才到!",
"reward_time": "2020-05-29T15:20:53",
"price": 5,
"category": "食品",
"image": null
},
{
"commodity_name": "鹿皮棉袄",
"grade": "四星好评",
"reward_content": "宝贝非常好,质量不错,下次继续买你家的,不过物流太慢了,好几天才到!",
"reward_time": "2020-05-28T15:20:53",
"price": 300,
"category": "衣服",
"image": null
},
]

随便举个例子。

但是现在,我前端采用了流加载,通过触发临界点,再次发送请求,此时我是不是需要知道,我到底一共要发送多少次请求,才能完全加载完数据,因此我还需要知道最大页数,也将其序列化,然后前端获取进行解析。


所以我原来的浏览记录的序列器就满足不了了,因为我是继承的serializer.ModelSerializer,因此我需要重新generate一个新的序列器,既包含了原来的浏览记录的序列器,也具备最大pages的可序列化,因此我就需要将浏览记录的序列器适配到这个新的序列器中。使之满足我的最新的需求。

这样一来,新的序列器的数据就可以是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"page": 2,
"data": [
{
"commodity_name": "旺仔牛奶",
"grade": "四星好评",
"reward_content": "宝贝非常好,质量不错,下次继续买你家的,不过物流太慢了,好几天才到!",
"reward_time": "2020-05-29T15:20:53",
"price": 5,
"category": "食品",
"image": null
},
{
"commodity_name": "鹿皮棉袄",
"grade": "四星好评",
"reward_content": "宝贝非常好,质量不错,下次继续买你家的,不过物流太慢了,好几天才到!",
"reward_time": "2020-05-28T15:20:53",
"price": 300,
"category": "衣服",
"image": null
},
]
}

说明:

这样的序列化嵌套其实就是一个适配器的设计模式,将两个或多个不相关的序列化器接口进行包装,使之满足用户所期待的形式。


二、如何实现这种嵌套的序列器

我想要实现这种model和自定义序列器进行组合的效果。一开始的做法是,通过序列化器类的context属性(注:ModelSerializer不存在context属性,因为它直接继承与Field类),将我所需要的数据以额外参数的形式传递进去,类似这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
serializer = PageSerializer(context={'serializer': self.get_serializer_class,
'instances': instances,'page':page})

class PageSerializer(serializers.Serializer):
"""页数序列器"""

page = serializers.SerializerMethodField()

data = serializers.SerializerMethodField()

@property
def serializer_class(self):
"""data serializer"""
return self.context.get('serializer')

def get_page(self, obj):
page = self.context.get('page')
return page

def get_data(self, obj):
instances = self.context.get('instances')
return self.serializer_class(instances, many=True).data

但是后来使用了只传入context,而不传入instances和data参数的这种序列化器方式,根本产生不了相应的任何数据,也就是调用序列化器对象的data获取不到序列化结果。可是理论上这样做是不会有问题的呀,我决定去源码探索一番。


三、serializer的源码分析


①、我们首先来看BaseSerilaizer的构造函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def __init__(self, instance=None, data=empty, **kwargs):
self.instance = instance # model实例或普通的python对象
if data is not empty:
self.initial_data = data # 可以是HttpRequest的POST,DELETE,PUT,GET请求传进来的参数。
self.partial = kwargs.pop('partial', False)
self._context = kwargs.pop('context', {}) # 额外字段
kwargs.pop('many', None)
super().__init__(**kwargs)

def __new__(cls, *args, **kwargs):
# We override this method in order to automagically create
# `ListSerializer` classes instead when `many=True` is set.
if kwargs.pop('many', False):
return cls.many_init(*args, **kwargs)
return super().__new__(cls, *args, **kwargs)

说明:

产生序列化的实例,首先会调用__new__内置方法,用来根据不同的参数选择我要如何实例化,这里会根据传进来的many参数进行选择,many参数表示我要序列化的对象是否是存在多个,以便创建ListSerializer序列化然后返回实例化对象。接下来调用__init__方法进行初始化,取出这些参数保存下来,但这些参数都不是必填的,拓展性很好,最后调用基类Field__init__方法。


②、接下来,我们来看一些序列化器的结果是如何产生的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@property
def data(self):
# 如果传过来data数据,就会赋值给initial_data,但是需要经过is_valid()方法根据校验器验证后,才会将验证后的值赋给_validated_data,也就是说,传进来data参数,必须调用is_valid方法进行校验
if hasattr(self, 'initial_data') and not hasattr(self, '_validated_data'):
msg = (
'When a serializer is passed a `data` keyword argument you '
'must call `.is_valid()` before attempting to access the '
'serialized `.data` representation.\n'
'You should either call `.is_valid()` first, '
'or access `.initial_data` instead.'
)
raise AssertionError(msg)
# 如果一开始没有_data属性,而且我没有找到类中定义了_data属性,所以它总是会调用这个方法,生成新的序列化器结果
if not hasattr(self, '_data'):
# 如果传入instances,以及没有错误产生,就对instances进行序列化,将结果发返回给_data
if self.instance is not None and not getattr(self, '_errors', None):
self._data = self.to_representation(self.instance)
# 如果传入data,以及没有错误提示,就将已经验证后的validated_data进行序列化,,将结果返回给_data
elif hasattr(self, '_validated_data') and not getattr(self, '_errors', None):
self._data = self.to_representation(self.validated_data)
# 其他情况,调用self.get_initial()方法进行_data构造
else:
self._data = self.get_initial()
return self._data

说明:

很明显,data是个只读特性,通过访问self.data来获取序列化后的数据,此时回到刚才的问题,我既没有传入instances和data,只是传入了context的字典,那么可以看到我会调用self._data = self.get_initial()来获取数据,那么这个self.get_initial()又是什么呢?我们点进去看看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

initial = None
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):
self._creation_counter = Field._creation_counter
Field._creation_counter += 1

# If `required` is unset, then use `True` unless a default is provided.
if required is None:
required = default is empty and not read_only

# Some combinations of keyword arguments do not make sense.
assert not (read_only and write_only), NOT_READ_ONLY_WRITE_ONLY
assert not (read_only and required), NOT_READ_ONLY_REQUIRED
assert not (required and default is not empty), NOT_REQUIRED_DEFAULT
assert not (read_only and self.__class__ == Field), USE_READONLYFIELD

self.read_only = read_only
self.write_only = write_only
self.required = required
self.default = default
self.source = source
# 如果initial有值传进来则赋值,没有赋值为empty则赋值为None,因为类属性定义了initail为None
self.initial = self.initial if (initial is empty) else initial
self.label = label
self.help_text = help_text
self.style = {} if style is None else style
self.allow_null = allow_null

def get_initial(self):
"""
Return a value to use when the field is being returned as a primitive
value, without any object instance.
"""
# 完全自定义的参数,回调函数的结果为原始python数据对象
if callable(self.initial):
return self.initial()
return self.initial
1
2
3
4
5
6
7
8
class empty:
"""
This class is used to represent no data being provided for a given input
or output value.

It is required because `None` may be a valid input or output value.
"""
pass

分析:

上述代码是最顶层基类Field的源码,首先看下它的__init__方法很多,这里我下先只讲下initial,仔细的看可以发现initial默认参数为empty,它是一个空类,用来消除None可能作为一个参数值的冲突,表示不提供任何的数据进来,其实也是赋值为None,只是None可能作为空值,也可能作为有效的值。

然后看get_initial获取值,我们可以很容易的发现,这个返回的值,完全就是由我们自己传进来的值进行决定的,必须是python原始数据,同时传入initial参数的时候,不能够传入instances否则,initial会无效。这下我豁然开朗,终于明白为什么只传入context根本不会产生任何的data了,因为还需要传入自定义的initial参数。


使用另外一种方法进行序列化嵌套

知道了上述原理后,觉得完全自定义序列化格式确实比较灵活,但是总觉得不够解耦合。所以我最终还是使用了另外一种方式。

话不多说,上代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

class PageSerializer(serializers.Serializer):
"""页数序列器"""

page = serializers.IntegerField()

data = serializers.SerializerMethodField()

@property
def serializer_class(self):
"""data serializer"""
return self.context.get('serializer')

def get_data(self, obj):
instances = self.context.get('instances')
return self.serializer_class(instances, many=True).data


class Page:
"""the instance of page"""

def __init__(self, page):
self.page = page

说明:

我采用适配器设计模式,将不同的序列化器接口融合在一起,使之满足我的需求。在这里我创建了最基本的PageSerializer序列化器,但是我还想添加其他的序列化器,因此采用SerializerMethodField()自定义序列化方法,通过context传入instances和serializer,针对某个serializer和它所需要的instances进行序列化。

调用方法:

1
2
3
4
5
6

instances, pages = self.get_object(user, **data)
page = Page(page=pages)
serializer = self.get_ultimate_serializer(page, context={'serializer': self.get_serializer_class,
'instances': instances})

这样我既传入了instance,确保对page进行序列化,然后又对context中的序列器进行序列化,这样就形成了最终我所需要的序列化格式。


四、总结

总之通常来说是要传入instance或者data参数的,不然就必须传入initial参数,一个可以回调的自定义对象。

看一看源码还是可以学到很多的

好啦,最后再来分析下这个具体序列化的方法:to_representation

选自Serializer类的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def to_representation(self, instance):
"""
Object instance -> Dict of primitive datatypes.
"""
ret = OrderedDict() # 有序的字典对象
fields = self._readable_fields

for field in fields:
try:
attribute = field.get_attribute(instance) # 返回该字段的原始属性值
except SkipField:
continue

# We skip `to_representation` for `None` values so that fields do
# not have to explicitly deal with that case.
#
# For related fields with `use_pk_only_optimization` we need to
# resolve the pk value.
check_for_none = attribute.pk if isinstance(attribute, PKOnlyObject) else attribute
# 检查该属性值是否为None,如果为None,设置None,否则将该字段值调用field基类的to_representation进行序列化
if check_for_none is None:
ret[field.field_name] = None
else:
ret[field.field_name] = field.to_representation(attribute)

return ret