一、关于 JSON
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于 JavaScript Programming Language, Standard ECMA-262 3rd Edition – December 1999 的一个子集。JSON 采用完全独立于语言的文本格式,但是也使用了类似于 C 语言家族的习惯(包括 C, C++, C#, Java, JavaScript, Perl, Python等)。这些特性使 JSON 成为理想的数据交换语言。
如果我们要在不同的编程语言之间传递对象,就必须把对象序列化为标准格式,比如XML,但更好的方法是序列化为 JSON,因为 JSON 表示出来就是一个字符串,可以被所有语言读取,也可以方便地存储到磁盘或者通过网络传输。JSON 不仅是标准格式,并且比XML更快,而且可以直接在Web页面中读取,非常方便。
注意,虽然在 Python 中单引号和双引号的作用完全相同。但是在其它语言中双引号引起来的才是字符串,单引号引起来的是字符!例如 C,例如 json!所以很多时候你可能会碰到在做 Json 格式转换时,经常发生使用单引号,导致转换失败的问题。
JSON 表示的对象就是标准的 JavaScript 语言的对象,JSON 和 Python 内置的数据类型对应如下:
JSON类型 | Python类型 |
{} | dict |
[] | list |
“string” | str |
1234.56 | int或float |
true/false | True/False |
null | None |
二、JSON 编码和解码
使用 JSON 函数需要导入 json 库:import json
函数 | 描述 |
---|---|
json.dumps | 将 Python 对象编码成 JSON 字符串 |
json.loads | 将已编码的 JSON 字符串解码为Python对象 |
json.dumps
json.dumps 用于将 Python 对象编码成 JSON 字符串。
语法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
json.dumps( obj, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, encoding="utf-8", default=None, sort_keys=False, **kw ) |
实例
以下实例将数组编码为 JSON 格式数据:
1 2 3 4 5 |
#!/usr/bin/python import json data = ['foo', {'bar': ('baz', None, 1.0, 2)}] json = json.dumps(data) print(json) |
以上代码执行结果为:
1 |
["foo", {"bar": ["baz", null, 1.0, 2]}] |
使用参数让 JSON 数据格式化输出:
1 2 3 4 5 6 |
>>> import json >>> print(json.dumps({'a': 'Runoob', 'b': 7}, sort_keys=True, indent=4, separators=(',', ': '))) { "a": "Runoob", "b": 7 } |
通过输出的结果可以看出,简单类型通过 encode 之后跟其原始的 repr() 输出结果非常相似,但是有些数据类型进行了改变,例如上例中的元组则转换为了列表。在 json 的编码过程中,会存在从 Python 原始类型向 json 类型的转化过程,具体的转化参照上面的表格。
json.dumps 方法提供了很多好用的参数可供选择,比较常用的有 sort_keys(对 dict 对象进行排序,我们知道默认 dict 是无序存放的),separators,indent等参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
>>> import json >>> data1 = {'b':789,'c':456,'a':123} >>> data2 = {'a':123,'b':789,'c':456} >>> d1 = json.dumps(data1,sort_keys=True) >>> d2 = json.dumps(data2) >>> d3 = json.dumps(data2,sort_keys=True) >>> print(d1) {"a": 123, "b": 789, "c": 456} >>> print(d2) {"a": 123, "c": 456, "b": 789} >>> print(d3) {"a": 123, "b": 789, "c": 456} >>> print(d1==d2) False >>> print(d1==d3) True |
上例中,本来 data1 和 data2 数据应该是一样的,但是由于 dict 存储的无序特性,造成两者无法比较。因此两者可以通过排序后的结果进行存储就避免了数据比较不一致的情况发生,但是排序后再进行存储,系统必定要多做一些事情,也一定会因此造成一定的性能消耗,所以适当排序是很重要的。
indent 参数是缩进的意思,它可以使得数据存储的格式变得更加优雅。
1 2 3 4 5 6 7 8 9 |
>>> import json >>> data = {'b':789,'c':456,'a':123} >>> json = json.dumps(data,sort_keys=True,indent=4) >>> print(json) { "a": 123, "b": 789, "c": 456 } |
输出的数据被格式化之后,变得可读性更强,但是却是通过增加一些冗余的空白格来进行填充的。json 主要是作为一种数据通信的格式存在的,而网络通信是很在乎数据的大小的,无用的空格会占据很多通信带宽,所以适当时候也要对数据进行压缩。separator 参数可以起到这样的作用,该参数传递是一个元组,包含分割对象的字符串。
1 2 3 4 5 6 7 8 9 |
#!/usr/bin/python import json data = {'b':789,'c':456,'a':123} print('DATA:', repr(data)) print('repr(data) :', len(repr(data))) print('dumps(data) :', len(json.dumps(data))) print('dumps(data, indent=2) :', len(json.dumps(data, indent=4))) print('dumps(data, separators):', len(json.dumps(data, separators=(',',':')))) |
执行结果如下:
1 2 3 4 5 |
DATA: {'a': 123, 'c': 456, 'b': 789} repr(data) : 30 dumps(data) : 30 dumps(data, indent=2) : 46 dumps(data, separators): 25 |
通过移除多余的空白符,达到了压缩数据的目的,而且效果还是比较明显的。
另一个比较有用的 dumps 参数是 skipkeys,默认为 False。 dumps 方法存储 dict 对象时,key 必须是 str 类型,如果出现了其他类型的话,那么会产生 TypeError 异常,如果开启该参数,设为 True 的话,则会比较优雅的过度。
1 2 3 4 5 6 7 8 9 |
>>> import json >>> data = {'b':789,'c':456,(1,2):123} >>> print(json.dumps(data, skipkeys=True)) {"c": 456, "b": 789} >>> print(json.dumps(data, skipkeys=false)) Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'false' is not defined |
最后需要关注的点是 ensure_ascii 参数,由于默认是 True,对于中文默认输出 Unicode code point,如下:
1 2 |
>>> json.dumps({'name': '东青'}) '{"name": "\\u4e1c\\u9752"}' |
想要正常序列化显示中文字符,需要关闭 ensure_ascii 参数即可
1 2 |
>>> json.dumps({'name': '东青'}, ensure_ascii=False) '{"name": "东青"}' |
json.loads
json.loads 用于解码 JSON 数据,该函数返回 Python 字段的数据类型。
语法:
1 |
json.loads(s[, encoding[, cls[, object_hook[, parse_float[, parse_int[, parse_constant[, object_pairs_hook[, **kw]]]]]]]]) |
实例:
以下实例展示了 Python 如何解码 JSON 对象。
1 2 3 4 5 6 7 |
#!/usr/bin/python import json jsonData = '{"a":1,"b":2,"c":3,"d":4,"e":5}'; text = json.loads(jsonData) print(text) |
以上代码执行结果为:
1 |
{u'a': 1, u'c': 3, u'b': 2, u'e': 5, u'd': 4} |
loads 方法返回了原始的对象,但是仍然发生了一些数据类型的转化。比如,上例中转化为了 unicode 类型。从 json 到 Python 的类型转化参考上面表格。
三、JSON 进阶
Python 的 dict 对象可以直接序列化为 JSON 的 {},不过,很多时候,我们更喜欢用 class 表示对象,比如定义 Student 类,然后序列化:
1 2 3 4 5 6 7 8 9 10 |
import json class Student(object): def __init__(self, name, age, score): self.name = name self.age = age self.score = score s = Student('Bob', 20, 88) print(json.dumps(s)) |
运行代码,毫不留情地得到一个 TypeError:
1 2 3 |
Traceback (most recent call last): ... TypeError: <__main__.Student object at 0x10603cc50> is not JSON serializable |
错误的原因是 Student 对象不是一个可序列化为 JSON 的对象。
如果连 class 的实例对象都无法序列化为 JSON,这肯定不合理! 通过上面所提到的 json 和 Python 的类型转化对照表,可以发现,{}(object)类型是和 dict 相关联的,所以我们需要把我们自定义的类型转化为 dict,然后再进行处理。这里,有两种方法可以使用。
方法一:自己写转化函数
仔细看看 dumps() 方法的参数列表,可以发现,除了第一个必须的 obj 参数外,dumps() 方法还提供了一大堆的可选参数。这些可选参数就是让我们来定制 JSON 序列化。前面的代码之所以无法把 Student 类实例序列化为 JSON,是因为默认情况下,dumps()
方法不知道如何将 Student 实例变为一个 JSON 的{}
对象。其中可选参数 default 就是把任意一个对象变成一个可序列为 JSON 的对象,我们只需要为 Student 专门写一个转换函数,再把函数传进去即可:
1 2 3 4 5 6 |
def student2dict(std): return { 'name': std.name, 'age': std.age, 'score': std.score } |
这样,Student 实例首先被 student2dict() 函数转换成 dict,然后再被顺利序列化为 JSON:
1 2 |
>>> print(json.dumps(s, default=student2dict)) {"age": 20, "name": "Bob", "score": 88} |
不过,下次如果遇到一个 Teacher 类的实例,照样无法序列化为 JSON。我们可以偷个懒,把任意 class 的实例变为 dict:
1 2 3 4 5 |
>>> s.__dict__ {'age': 20, 'score': 88, 'name': 'Bob'} >>> print(json.dumps(s, default=lambda obj: obj.__dict__)) {"age": 20, "score": 88, "name": "Bob"} |
因为通常class的实例都有一个__dict__
属性,它就是一个dict
,用来存储实例变量。也有少数例外,比如定义了__slots__
的 class。
同样的道理,如果我们要把 JSON 反序列化为一个 Student 对象实例,loads()
方法首先转换出一个dict
对象,然后,我们传入的object_hook
函数负责把dict
转换为 Student 实例:
1 2 |
def dict2student(d): return Student(d['name'], d['age'], d['score']) |
运行结果如下:
1 2 3 4 |
>>> json_str = '{"age": 20, "score": 88, "name": "Bob"}' >>> print(json.loads(json_str, object_hook=dict2student)) <__main__.Student object at 0x10cd3c190> |
打印出的是反序列化的 Student 实例对象。
方法二:继承 JSONEncoder 和 JSONDecoder 类,重写相关方法
JSONEncoder 类负责编码,主要是通过其 default 函数进行转化,我们可以 override 该方法。同理对于 JSONDecoder。
1 2 3 4 5 6 |
class myencoder(json.JSONEncoder): def default(self, obj): d = {} d['__class__'] = obj.__class__.__name__ d['__module__'] = obj.__module__ d.update(obj.__dict__) |
当继承 JSONEncoder 类后,我们就可以在 default 函数内做一些额外的处理,然后使用下面的方式解码出来。
1 2 |
>>> json.dumps(s, cls=myencoder) '{"__class__": "Student", "__module__": "__main__", "name": "Bob", "age": 20, "score": 88}' |
或
1 2 |
>>> myencoder().encode(s) '{"__class__": "Student", "__module__": "__main__", "name": "Bob", "age": 20, "score": 88}' |
利用继承的方式,我们可以很好地把时间类型转换为字符串类型,如下代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import json import datetime class DateEncoder(json.JSONEncoder): ''' 序列化时间类型 ''' def default(self, o): if isinstance(o, datetime.datetime) \ or isinstance(o, datetime.date) \ or isinstance(o, datetime.timedelta): return o.__str__() return json.JSONEncoder.default(self, o) d = {'id1': datetime.datetime(2018, 6, 27, 9, 32, 5), 'id2': datetime.datetime(2018, 6, 27, 9, 32, 2)} print(json.dumps(d, cls=DateEncoder)) |
四、simplejson
simplejson 是一种简单,快速,可扩展的第三方 JSON 编码器/解码器。
simplejson 在使用在与原生 JSON 差不多,你如果有做数据库查询展示的需求,simplejson 可以很好地处理 bigint 类型,不会丢失精度。如果使用原生 json 就会丢失 bigint 精度。
<参考>
http://github.com/simplejson/simplejson
http://www.runoob.com/python/python-json.html