DRF的to_representation和to_internal_value是序列化和反序列化过程中最核心的方法,它们分别用于将数据对象转换成字典,和将字典转换成数据对象。
DRF所有序列化器类都继承了BaseSerializer类,通过重写该类的to_representation()和to_internal_value()方法可以改变序列化和反序列化的行为,比如给序列化后的数据添加额外的数据,或者对客户端API请求携带的数据进行反序列化处理以及用来自定义序列化器字段。
to_representation的作用是将查询集合、模型实例、JSON等数据类型,转换成DRF内置的Serializer所支持的数据类型(比如嵌套的Serializer、List、Dict等),从而可以对这些数据进行序列化。
to_internal_value的作用是将传入的字典数据(比如通过API POST提交的JSON数据),转换成需要保存到数据库的原生数据类型(比如序列化后需要保存到Model中的IntegerField、CharField等)。
重写这些方法的作用是可以自定义序列化和反序列化过程中的数据转换规则,从而可以满足各种不同的业务需求。比如可以根据不同的角色返回不同的数据、动态过滤掉某些字段、根据某些字段的取值计算生成额外的字段等。
假设有如下一个文章模型(Article):
class Article2(models.Model):
title = models.CharField(max_length=100)
body = models.TextField()
like_by = models.ManyToManyField(to=User)
def __str__(self):
return self.title
每个文章资源有title, body和liked_by 三个字段。liked_by代表喜欢该文章的用户对象id列表。
序列化器ArticleSerializer类如下所示:
class ArticleSerializer8(serializers.ModelSerializer):
class Meta:
model = Article
fields = '__all__'
如果使用上面序列化器去序列化单篇文章资源,将得到如下输出数据:
{
"id": 1,
"title": "DRF advanced tutorials",
"body": "This is a good example.",
"liked_by": [
2,
3,
4
]
}
如果希望给上面输出数据添加一个total_likes点赞总数的字段,只需要在序列化器类里重写to_representation方法。
class ArticleSerializer9(serializers.ModelSerializer):
class Meta:
model = Article
fields = '__all__'
def to_representation(self, instance):
# 调用父类获取当前序列化数据,instance代表每个对象实例obj
data = super().to_representation(instance)
# 对序列化数据做修改,添加新的数据
data['total_likes'] = instance.liked_by.count()
return data
现在使用新的序列化器类去序列化单篇文章资源,将得到如下输出结果。to_representation() 方法改变了序列化的输出,并传递了额外的数据。
{
"id": 1,
"title": "DRF advanced tutorials",
"body": "This is a good example.",
"liked_by": [
2,
3,
4
],
"total_likes": 3
}
to_internal_value主要在反序列化时用到,其作用处理API请求携带的数据,对其进行验证并转化为Python的数据类型。
假如API客户端通过请求提交了额外的数据,比如extra_info字段,如下所示
{
"extra_info": {
"msg": "Hello world!"
},
"article_data": {
"id": 1,
"title": "DRF advanced tutorials",
"body": "This is a good example.",
"liked_by": [
2,
3,
4
],
"total_likes": 3
}
}
由于extra_info字段不属于ArticleSerializer类里的字段,如果直接使用ArticleSerializer类对上述数据进行反序列化会出现错误。
事实上反序列化时只需要提取article_data然后对其反序列化即可,所以可以重写to_internal_value提取所需要的数据,忽略不想要的数据。
class ArticleSerializer10(serializers.ModelSerializer):
class Meta:
model = Article
fields = '__all__'
def to_internal_value(self, data):
# 提取所需要的数据,对其进行反序列化,data代表未验证的数据
article_data = data.get('article_data', '')
return super().to_internal_value(article_data)
to_representation() 和to_internal_value()方法的另一个重要用途就是用来自定义序列化类字段。下例为DRF提供的一个官方演示,展示了如何使用这两个方法自定义了一个包含有x, y坐标的字段CoordinateField字段。
# 自定义序列化器类字段
class CoordinateField(serializers.Field):
def to_representation(self, value):
ret = {
'x': value.x_coordinate,
'y': value.y_coordinate
}
return ret
def to_internal_value(self, data):
ret = {
'x_coordinate': data['x'],
'y_coordinate': data['y']
}
return ret
定义好后,可以在序列化类中使用。
from .models import DataPoint
# 使用上述自定义序列化器类的字段
class DataPointSerializer(serializers.ModelSerializer):
coordinates = CoordinateField(source='*')
class Meta:
model = DataPoint
fields = ['label', 'coordiante']