一文看懂Python及Django不同类型数据的json序列化
何谓序列化(serialization)
每种编程语言都有各自的数据类型, 将属于自己语言的数据类型或对象转换为可通过网络传输或可以存储到本地磁盘的数据格式(如:XML、JSON或特定格式的字节串)的过程称为序列化(seralization);反之则称为反序列化。
Python的JSON模块
python自带的json库(无需额外安装), 主要包含了dumps, loads, dump和load四种方法其作用分别如下所示。
-
json.loads() - 将json字符串转换为python数据类型
-
json.dumps() - 将python数据类型转化为json字符串
-
json.dump() - 将python输入转化为json格式存入磁盘文件
-
json.load() - 将磁盘文件中json格式数据转换为python数据类型
python数据格式与json数据格式对应转换关系如下:
Python | JSON |
---|---|
dict | Object |
list, tuple | array |
str | string |
int, float, | numbers |
True | true |
False | false |
None | null |
你注意到了吗? 还有很多python数据类型(set, datetime)不在上表中哦。
json的模块dumps方法介绍 - python数据的序列化
json模块的dumps方法可以将python常用数据格式转化为json格式。该方法还提供了很多可选参数如ident, separators, ensure_ascii, sort_keys和default参数。这些参数都非常有用,我们会稍后逐一介绍。
dumps(obj, skipkeys=False, ensure_ascii=True,
check_circular=True, allow_nan=True,
cls=None, indent=None, separators=None,
default=None, sort_keys=False, **kw)
我们先来看看一个最简单的例子。你注意到了吗? 生成的json格式数据外面都加了单引号,这说明dict类型数据已经转化成了json字符串。
>>> import json
>>> json.dumps({"name":"John", "score": 112})
'{"name": "John", "score": 112}'
如果一个dict很长,生成的json字符串会非常长,这时我们可以设置indent参数使生成的json格式数据更优美,更容易人们阅读。代码如下所示:
>>> import json
>>> json.dumps({"name":"John", "score": 112}, indent=4)
'{\n "name": "John",\n "score": 112\n}'
>>> print(json.dumps({"name":"John", "score": 112}, indent=4))
{
"name": "John",
"score": 112
}
然而使用indent参数的代价是json字符串里增加了额外的空白,机器阅读根本不需要它们,
即使不用indent参数,你会发现dumps生成的json字符串中的','号和':'号分隔符后都会附加一个默认空白字符,我们可以通过separators参数重新指定分隔符,从而去除无用的空白字符,如下所示。这样可以减少无用数据的的传输,节省带宽增加数据传输速度。
>>> import json
>>> json.dumps({"name":"John", "score": 112})
'{"name": "John", "score": 112}'
# 使用separators选项
>>> json.dumps({"name":"John", "score": 112}, separators=(',',':'))
'{"name":"John","score":112}'
如果字符串有非ASCII字符(比如中文),它们在json序列化时都会被转义成'\uXXXX'组成的ascii字符串。如果想得到更加易读的字符串,可以设置ensure_ascii=False。
>>> import json
>>> json.dumps({"Name":"小明", "Age": 16})
'{"Name": "\\u5c0f\\u660e", "Age": 16}'
# 设置ensure_ascii=False
>>> json.dumps({"Name":"小明", "Age": 16}, ensure_ascii=False)
'{"Name": "小明", "Age": 16}'
一般的dict默认是无序的,你还可以设置sort_keys = True对生成的json格式数据进行排序,这里就不演示了。default参数我们后面会重点介绍。
json模块的dump,loads和load方法介绍
与dumps方法不同,json模块的dump方法用于将生成的json数据写入磁盘文件。其用法和dumps类似,唯一不同的是需要指定需要写入的文件,具体用法如下所示:
import json
with open("json.txt", 'w') as f:
json.dump({"Name":"小明", "Age": 16}, f, ensure_ascii=True)
json的loads方法用于将json格式数据转化为python格式,实现数据的反序列化,如下所示。千万别忘了在json符串外的单引号哦。
>>> import json
>>> json.loads('{"Name": "小明", "Age": 16}')
{'Name': '小明', 'Age': 16}
json的load方法与loads用法相似,不过它需要指定存有json数据的文件。
>>> import json
>>> with open("json.txt", 'r') as f:
json.load(f)
很多python格式数据不能直接被dumps方法序列化
很多python数据类型(比如日期,集合和自定义的类)并不能直接被dumps方法序列化,这时会出现 xxx is not JSON serializable的错误,如下面代码所示。当出现类似错误时,我们有两种解决方案。
-
通过数据类型转换函数实现
-
通过继承JSONEncoder和JSONDecoder类实现
>>> import json
>>> from datetime import datetime
# DateTime类型
>>> json.dumps({"date":datetime.now()})
Traceback (most recent call last):
TypeError: Object of type 'datetime' is not JSON serializable
# 自定义的User类
>>> class User(object):
def __init__(self, name):
self.name = name
>>> json.dumps(User("John"))
Traceback (most recent call last):
TypeError: Object of type 'User' is not JSON serializable
解决方案一: 编写数据类型转换函数
该方法的工作原理是先编写数据类型转化函数,通过设置dumps方法里的default参数调用格式转化函数,将dumps方法不支持的数类型先转化为字符串格式,再实现json序列化。
# 将datetime格式数据json化
>> > import json
>> > from datetime import datetime
>> > def date_to_str(obj):
if isinstance(obj, datetime):
return obj.strftime('%Y-%m-%d %H:%M:%S')
elif isinstance(obj, date):
return obj.strftime('%Y-%m-%d')
return TypeError
>> > json.dumps({"date": datetime.now()}, default=date_to_str)
'{"date": "2018-09-22 21:25:42"}'
# 将set类型数据json化
>> > import json
>> > set_data = {'my_set': {1, 2, 3}}
>> > def set_to_list(obj):
if isinstance(obj, set):
return list(obj)
raise TypeError
>> > result = json.dumps(set_data, default=set_to_list)
对于我们自定义的类, 使用dumps方法时我们一般要先编写obj_to_dict方法,将object转化为字典dict再JSON序列化。同理,使用loads方法对json数据反序列化时,我们还需要编写dict_to_obj方法,通过default参数调用。下面这2段代码是比较通用的对象(object)与字典(dict)互转的经典代码,请用微信收藏后再看。
# 将自定义的类转化为字典,dumps方法使用
def obj_to_dict(obj):
d = {}
d['__class__'] = obj.__class__.__name__
d['__module__'] = obj.__module__
d.update(obj.__dict__)
return d
# 将字典转化为自定义的类,loads方法使用
def dict_to_obj(d):
if '__class__' in d:
class_name = d.pop('__class__')
module_name = d.pop('__module__')
module = __import__(module_name)
class_ = getattr(module, class_name)
args = dict((key.encode('ascii'), value) for key, value in d.items())
instance = class_(**args)
else:
instance = d
return instance
解决方案二: 继承JSONEncoder类和JSONDecode类
另一个解决方案是继承JSONEncoder类和JSONDecode类定义自己的编码Encoder类,然后使用cls=MyEncoder,来调用编码器。比如下例中我们定义了自己的DateTimeEncoder,将日期类型数据序列化。
from datetime import datetime, date
import json
class DateTimeEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime):
return obj.strftime('%Y-%m-%d %H:%M:%S')
elif isinstance(obj, date):
return obj.strftime('%Y-%m-%d')
return json.JSONEncoder.default(self, obj)
json_data = {'num': 1, 'date': datetime.now()}
print(json.dumps(json_data, cls=DateTimeEncoder))
对于自定义的对象,我们也可以通过继承JSONEncoder类实现它的序列化,如下所示:
import json
class User(object):
def __init__(self, name):
self.name = name
class MyJSONEncoder(json.JSONEncoder):
def default(self, obj):
d = {}
d['__class__'] = obj.__class__.__name__
d['__module__'] = obj.__module__
d.update(obj.__dict__)
return d
user = User("John")
json.dumps(user, cls=MyJSONEncoder))
对于简单的数据序列化,方案一更容易理解,代码也更少。但当需要传输的数据很大时,使用继承JSONEncoder类来实现序列化时有个很大的好处,就是可以通过iterencode()方法把一个很大的数据对象分多次进行序列化,这对于网络持续传输或写入大的文件非常有用。如下所示。
>>> for chunk in MyJSONEncoder().iterencode(big_object):
... print(chunk)
Django特有数据类型序列化
Django编程就是是python编程,以上所介绍的序列化方法对django也是适用的。不同的是Django还有自己专属的数据类型比如QuerySet和ValueQuerySet类型数据,还提供了更便捷的serializers类。使用serializers类可以轻易将QuerySet格式的数据转化为json格式。
# Django Queryset数据 to Json
from django.core import serializers
data = serializers.serialize("json", SomeModel.objects.all())
data1 = serializers.serialize("json", SomeModel.objects.all(), fields=('name','id'))
data2 = serializers.serialize("json", SomeModel.objects.filter(field = some_value))
有时候我们只需要查询结果集的部分字段,可以使用values('字段名','字段名2')来要求返回的是哪些列的数据.但是返回来的是ValuesQuerySet对象而不是QuerySet对象。ValuesQuerySet对象不能用 serializers.serialize() 方法序列化成json, 需要先转换成list再用 json.dumps()方法序列化成json格式。示例代码如下所示:
import json
from django.core.serializers.json import DjangoJSONEncoder
queryset = myModel.objects.filter(foo_icontains=bar).values('f1', 'f2', 'f3')
data4 = json.dumps(list(queryset), cls=DjangoJSONEncoder)
django-rest-framework
如果你要利用django开发restful的web API, 为不同客户端提供序列化过的json格式数据,django-rest-framework才是你真正需要的序列化工具。与django自带的serializers类相比,rest framework支持用户验证,查询过滤和符合restful规范的url设计,我们后面会专门介绍。欢迎关注我的微信。
小结
我们介绍了何为JSON序列化以及python json模块的dumps, loads, dump和load方法。我们还介绍了如何将dumps方法不支持的数据格式(如日期,集合, 自定义的类和Django的QuerySet类型)如何通过要自定义格式转化函数和继承JsonEncoder类将它们转化为json格式。希望本文对你有所帮助。喜欢的给个赞吧!