首页 > 代码库 > Python 中数据的序列化和反序列化(json处理)

Python 中数据的序列化和反序列化(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成为理想的数据交换语言。

JSON建构于两种结构:

  • “名称/值”对的集合(A collection of name/value pairs)。不同的语言中,它被理解为对象(object),纪录(record),结构(struct),字典(dictionary),哈希表(hash table),有键列表(keyed list),或者关联数组 (associative array)。
  • 值的有序列表(An ordered list of values)。在大部分语言中,它被理解为数组(array)。

这些都是常见的数据结构。事实上大部分现代计算机语言都以某种形式支持它们。这使得一种数据格式在同样基于这些结构的编程语言之间交换成为可能。

 

序列化(Serialization):将对象的状态信息转换为可以存储或可以通过网络传输的过程,传输的格式可以是JSON、XML等。反序列化就是从存储区域(JSON,XML)读取反序列化对象的状态,重新创建该对象。

Python2.6开始加入了JSON模块,无需另外下载,Python的Json模块序列化与反序列化的过程分别是 encoding和 decoding

encoding:把一个Python对象编码转换成Json字符串
decoding:把Json格式字符串解码转换成Python对象
对于简单数据类型(string、unicode、int、float、list、tuple、dict),可以直接处理。

json详细讲解参见:http://json.org/json-zh.html

Python操作json的标准api库参考:https://docs.python.org/3/library/json.html

 

1、简单数据类型的encoding和decoding:

>>> import json
>>> obj = [[1,2,3], 12, 12.3, ‘abc‘, {123:(1,2,3), ‘key1‘:[1,2], ‘key2‘:[3,4]}]
>>> encodedjson = json.dumps(obj)
>>> print(repr(obj))
[[1, 2, 3], 12, 12.3, ‘abc‘, {‘key1‘: [1, 2], 123: (1, 2, 3), ‘key2‘: [3, 4]}]
>>> print(encodedjson)
[[1, 2, 3], 12, 12.3, "abc", {"key1": [1, 2], "123": [1, 2, 3], "key2": [3, 4]}]
>>> 

简单类型的数据通过encode之后与原始的repr()输出结果神似;部分数据类型存在差异,例如上例中的元组转换为列表,字典中键为123的整型转化为字符串类型。在json的编码过程中,会存在从python原始类型向json类型的转化过程,具体的转化对照如下:

技术分享

json.dumps()方法返回了一个str对象encodedjson,json.loads()函数可以将encodedjson进行decode,得到原始数据:

>>> decodedjson = json.loads(encodedjson)
>>> print(type(decodedjson))
<class ‘list‘>
>>> print(decodedjson)
[[1, 2, 3], 12, 12.3, ‘abc‘, {‘key1‘: [1, 2], ‘123‘: [1, 2, 3], ‘key2‘: [3, 4]}]
>>> print(decodedjson[4][‘123‘])
[1, 2, 3]
>>> 

注意上述decodedjson[4][‘123‘]的键仍为字符串,并未转化为原始的数据类型(int),建议在字典key中采用基本的字符串类型。

>>> kk = {1:[1,2,3],False:[3,5],None:2}
>>> kkd = json.dumps(kk)
>>> print(kkd)
{"false": [3, 5], "1": [1, 2, 3], "null": 2}
>>> kkl = json.loads(kkd)
>>> print(kkl)
{‘1‘: [1, 2, 3], ‘null‘: 2, ‘false‘: [3, 5]}
>>> 

从json到python的类型转化对照如下:

技术分享

json.dumps方法提供了很多好用的参数可供选择,比较常用的有sort_keys(对dict对象进行排序,我们知道默认dict是无序存放的),separators,indent等参数。

排序功能使得存储的数据更加有利于观察,也使得对json输出的对象进行比较,例如:

>>> data_1 = {‘b‘:789, ‘c‘:456, ‘a‘:123}
>>> data_2 = {‘a‘:123, ‘b‘:789, ‘c‘:456}
>>> d1 = json.dumps(data_1, sort_keys=True)
>>> d2 = json.dumps(data_2)
>>> d3 = json.dumps(data_2, sort_keys=True)
>>> print(d1)
{"a": 123, "b": 789, "c": 456}
>>> print(d2)
{"c": 456, "b": 789, "a": 123}
>>> print(d3)
{"a": 123, "b": 789, "c": 456}
>>> print(d1 == d2)
False
>>> print(d1 == d3)
True
>>> print(data_1 == data_2)
True
>>> 

上例中,本来data1和data2数据应该是一样的,但是由于dict存储的无序特性,造成两者无法比较。因此两者可以通过排序后的结果进行存储就避免了数据比较不一致的情况发生,但是排序后再进行存储,系统必定要多做一些事情,也一定会因此造成一定的性能消耗,所以适当排序是很重要的。

indent参数是缩进的意思,它可以使得数据存储的格式变得更加优雅。

>>> data_1 = {‘b‘:789, ‘c‘:456, ‘a‘:123}
>>> d1 = json.dumps(data_1, sort_keys=True, indent=4)
>>> print(d1)
{
    "a": 123,
    "b": 789,
    "c": 456
}
>>>

输出的数据被格式化之后,可读性更强,但是需要增加一些冗余的空白符进行填充的。json作为一种数据通信的格式,而网络通信是很在乎数据的大小的,无用的空格会占据更多的通信带宽,所以适当时候需要对数据进行压缩。separator参数可以起到这样的作用,该参数传递一个元组,包含分割对象的字符串。

>>> data = {‘b‘:789, ‘c‘:456, ‘a‘:123}
>>> print(‘DATA:‘,repr(data))
DATA: {‘c‘: 456, ‘a‘: 123, ‘b‘: 789}
>>> print(‘repr(data)             :‘, len(repr(data)))
repr(data)             : 30
>>> print(‘dumps(data)            :‘, len(json.dumps(data)))
dumps(data)            : 30
>>> print(‘dumps(data, indent=4)  :‘, len(json.dumps(data, indent = 4)))
dumps(data, indent=4)            : 44
>>> print(‘dumps(data, separators):‘, len(json.dumps(data, separators=(‘,‘, ‘:‘))))
dumps(data, separators : 25
>>> 

另一个比较有用的dumps参数是skipkeys,默认为False。 dumps方法存储dict对象时,key必须是str类型,如果出现了其他类型的话,那么会产生TypeError异常,如果开启该参数,设为True的话,则会比较优雅的过度。

>>> data = http://www.mamicode.com/{‘b‘:789,‘c‘:456,(1,2):123, 456:678}"<pyshell#12>", line 1, in <module>
    print(json.dumps(data))
  File "D:\Python\lib\json\__init__.py", line 230, in dumps
    return _default_encoder.encode(obj)
  File "D:\Python\lib\json\encoder.py", line 192, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "D:\Python\lib\json\encoder.py", line 250, in iterencode
    return _iterencode(o, 0)
TypeError: keys must be a string
>>> print(json.dumps(data, skipkeys=True))
{"456": 678, "b": 789, "c": 456}
>>>

 

2、处理自定义的数据类型:

json模块不仅可以处理普通的python内置类型,也可以处理我们自定义的数据类型,而往往处理自定义的对象是很常用的。

首先,我们在Person模块中定义一个类Person。

#!/usr/local/bin/python3
# -*- coding:utf-8 -*-
# autor:antclonies

import json

class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age 
    def __repr__(self):
        return ‘Person Object name : %s, age : %d‘%(self.name, self.age)

if __name__ == ‘__main__‘:
    p = Person(‘Peter‘, 22) 
    print(p)
    dmp = json.dumps(p)
    print(dmp)

运行以上模块:

[root@ant-colonies tmp]# ./py_2.py 
Person Object name : Peter, age : 22
Traceback (most recent call last):
 ...
  File "/usr/local/lib/python3.5/json/encoder.py", line 179, in default
    raise TypeError(repr(o) + " is not JSON serializable")
TypeError: Person Object name : Peter, age : 22 is not JSON serializable

如果直接通过json.dumps方法对Person的实例进行处理的话,会报错,因为json不知道怎样转化这个Person类型的实例p。通过上面所提到的json和python的类型转化对照表,可以发现,object类型和dict相关联,所以我们需要把我们自定义的类型转化为dict,然后再进行处理。这里,有两种方法可以使用。

方法一:自己写转化函数

#!/usr/local/bin/python3
# -*- coding:utf-8 -*-
#

import json
import Person

p = Person.Person(‘Peter‘, 22)

def obj2dict(obj):
	‘‘‘convert object to dict‘‘‘
	d = {}
	d[‘__class__‘] = obj.__class__.__name__
	d[‘__module__‘] = obj.__module__
	d.update(obj.__dict__)
	return d

def dict2obj(dmp):
	‘‘‘convert dict to object‘‘‘
	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, value) for key, value in d.items())   # get args
		inst = class_(**args)  # create new instance
	else:
		inst = d
	return inst

d = obj2dict(p)
print(d)
‘‘‘{‘__module__‘: ‘Person‘, ‘__class__‘: ‘Person‘, ‘name‘: ‘Peter‘, ‘age‘: 22}‘‘‘

o = dict2obj(d)
print(type(o), o)
‘‘‘<class ‘Person.Person‘> Person Object name : Peter, age : 22‘‘‘

dump = json.dumps(p, default=obj2dict)
print(dump)
‘‘‘{"__module__": "Person", "__class__": "Person", "name": "Peter", "age": 22}‘‘‘

load = json.loads(dump, object_hook=dict2obj)
print(load)
‘‘‘{‘name‘: ‘Peter‘, ‘age‘: 22}‘‘‘

上面代码已经写的很清楚了,实质就是自定义object类型和dict类型进行转化。object2dict函数将对象模块名、类名以及__dict__存储在dict对象里,并返回。dict2object函数则是反解出模块名、类名、参数,创建新的对象并返回。在json.dumps 方法中增加default参数,该参数表示在转化过程中调用指定的函数,同样在decode过程中json.loads方法增加object_hook,指定转化函数。

方法二:继承JSONEncoder和JSONDecoder类,覆写相关方法

JSONEncoder类负责编码,主要是通过其default函数进行转化,我们可以override该方法。同理对于JSONDecoder。

#!/usr/local/bin/python3
# -*- coding:utf-8 -*-
#

import json
import Person

p = Person.Person(‘Peter‘, 22)

class MyEncoder(json.JSONEncoder):
	def default(self, obj):
		‘‘‘convert object to dict‘‘‘
		d = {}
		d[‘__class__‘] = obj.__class__.__name__
		d[‘__module__‘] = obj.__module__
		d.update(obj.__dict__)
		return d

class MyDecoder(json.JSONDecoder):
	def __init__(self):
		json.JSONDecoder.__init__(self, object_hook=self.dict2obj)
	def dict2obj(self, d):
		‘‘‘convert dict to object‘‘‘
		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, value) for key, value in d.items())   # get args
			inst = class_(**args)  # create new instance
		else:
			inst = d
		return inst

d = MyEncoder().encode(p)
o = MyDecoder().decode(d)

print(d)
print(type(o), o)

‘‘‘
{"name": "Peter", "age": 22, "__module__": "Person", "__class__": "Person"}
<class ‘Person.Person‘> Person Object name : Peter, age : 22
‘‘‘
 

参考了:

http://www.cnblogs.com/coser/archive/2011/12/14/2287739.html
https://docs.python.org/3/library/json.html
http://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/00143192607210600a668b5112e4a979dd20e4661cc9c97000

Python 中数据的序列化和反序列化(json处理)