1.什么是JSON
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。它基于JavaScript的一个子集。 JSON采用完全独立于语言的文本格式,但是也使用了类似于C语言家族的习惯(包括C, C++, C#, Java, JavaScript, Perl, Python等)。这些特性使JSON成为理想的数据交换语言。易于人阅读和编写,同时也易于机器解析和生成。2.JSON语法规则
JSON 语法是 JavaScript 对象表示法语法的子集。有如下特性:
1.数据在名称/值对中
2.数据由逗号分隔
3.花括号保存对象
4.方括号保存数组
JSON 数据的书写格式是:名称/值 对。
名称/值对包括字段名称(在双引号中),后面写一个冒号,然后是值:
"firstName" : "John"
这很容易理解,等价于这条 JavaScript 语句:
firstName = "John"
JSON 值可以是:
1.数字(整数或浮点数) 2.字符串(在双引号中)
3.逻辑值(true 或 false) 4.数组(在方括号中)
5.对象(在花括号中) 6.null
JSON在线校验格式化工具: bejson
JSON建构有两种结构
JSON简单说就是javascript中的对象和数组,所以这两种结构就是对象和数组两种结构,通过这两种结构可以表示各种复杂的结构。
1、对象:对象在js中表示为“{}”括起来的内容,数据结构为 {key:value,key:value,...}的键值对的结构,在面向对象的语言中,key为对象的属性,value为对应的属性值,所以很容易理解,取值方法为 对象.key 获取属性值,这个属性值的类型可以是 数字、字符串、数组、对象几种。
2、数组:数组在js中是中括号“[]”括起来的内容,数据结构为 ["java","javascript","vb",...],取值方式和所有语言中一样,使用索引获取,字段值的类型可以是 数字、字符串、数组、对象几种。
经过对象、数组2种结构就可以组合成复杂的数据结构了。
4.JSON基础示例
简单地说,JSON 可以将 JavaScript 对象中表示的一组数据转换为字符串,然后就可以在函数之间轻松地传递这个字符串,或者在异步应用程序中将字符串从 Web 客户机传递给服务器端程序。这个字符串看起来有点儿古怪,但是JavaScript很容易解释它,而且 JSON 可以表示比"名称 / 值对"更复杂的结构。例如,可以表示数组和复杂的对象,而不仅仅是键和值的简单列表。
名称 / 值对
按照最简单的形式,可以用下面这样的 JSON 表示"名称 / 值对":
{ "firstName": "Brett" }
这个示例非常基本,而且实际上比等效的纯文本"名称 / 值对"占用更多的空间:
firstName=Brett
但是,当将多个"名称 / 值对"串在一起时,JSON 就会体现出它的价值了。首先,可以创建包含多个"名称 / 值对"的 记录,比如:
{ "firstName": "Brett", "lastName":"McLaughlin", "email": "aaaa" }
从语法方面来看,这与"名称 / 值对"相比并没有很大的优势,但是在这种情况下 JSON 更容易使用,而且可读性更好。例如,它明确地表示以上三个值都是同一记录的一部分;花括号使这些值有了某种联系。
表示数组
当需要表示一组值时,JSON 不但能够提高可读性,而且可以减少复杂性。例如,假设您希望表示一个人名列表。在XML中,需要许多开始标记和结束标记;如果使用典型的名称 / 值对(就像在本系列前面文章中看到的那种名称 / 值对),那么必须建立一种专有的数据格式,或者将键名称修改为 person1-firstName这样的形式。
如果使用 JSON,就只需将多个带花括号的记录分组在一起:
- {
- "people": [
- { "firstName": "Brett", "lastName":"McLaughlin", "email": "aaaa" },
- { "firstName": "Jason", "lastName":"Hunter", "email": "bbbb"},
- { "firstName": "Elliotte", "lastName":"Harold", "email": "cccc" }
- ]
- }
这不难理解。在这个示例中,只有一个名为 people的变量,值是包含三个条目的数组,每个条目是一个人的记录,其中包含名、姓和电子邮件地址。上面的示例演示如何用括号将记录组合成一个值。当然,可以使用相同的语法表示多个值(每个值包含多个记录):
- { "programmers": [
- { "firstName": "Brett", "lastName":"McLaughlin", "email": "aaaa" },
- { "firstName": "Jason", "lastName":"Hunter", "email": "bbbb" },
- { "firstName": "Elliotte", "lastName":"Harold", "email": "cccc" }
- ],
- "authors": [
- { "firstName": "Isaac", "lastName": "Asimov", "genre": "science fiction" },
- { "firstName": "Tad", "lastName": "Williams", "genre": "fantasy" },
- { "firstName": "Frank", "lastName": "Peretti", "genre": "christian fiction" }
- ],
- "musicians": [
- { "firstName": "Eric", "lastName": "Clapton", "instrument": "guitar" },
- { "firstName": "Sergei", "lastName": "Rachmaninoff", "instrument": "piano" }
- ] }
这里最值得注意的是,能够表示多个值,每个值进而包含多个值。但是还应该注意,在不同的主条目(programmers、authors 和 musicians)之间,记录中实际的名称 / 值对可以不一样。JSON 是完全动态的,允许在 JSON 结构的中间改变表示数据的方式。
在处理 JSON 格式的数据时,没有需要遵守的预定义的约束。所以,在同样的数据结构中,可以改变表示数据的方式,甚至可以以不同方式表示同一事物。
5.JSON和XML比较
可读性
JSON和XML的可读性可谓不相上下,一边是简易的语法,一边是规范的标签形式,很难分出胜负。
可扩展性
XML天生有很好的扩展性,JSON当然也有,没有什么是XML能扩展,而JSON却不能扩展的。不过JSON在Javascript主场作战,可以存储Javascript复合对象,有着xml不可比拟的优势。
编码难度、解码难度(略)
实例比较
XML和JSON都使用结构化方法来标记数据,下面来做一个简单的比较。
用XML表示中国部分省市数据如下:
- <?xml version="1.0" encoding="utf-8"?>
- <country>
- <name>中国</name>
- <province>
- <name>黑龙江</name>
- <cities>
- <city>哈尔滨</city>
- <city>大庆</city>
- </cities>
- </province>
- <province>
- <name>广东</name>
- <cities>
- <city>广州</city>
- <city>深圳</city>
- <city>珠海</city>
- </cities>
- </province>
- <province>
- <name>台湾</name>
- <cities>
- <city>台北</city>
- <city>高雄</city>
- </cities>
- </province>
- <province>
- <name>新疆</name>
- <cities>
- <city>乌鲁木齐</city>
- </cities>
- </province>
- </country>
用JSON表示如下:
- {
- "name":"中国",
- "province":[
- {
- "name":"黑龙江",
- "cities":{
- "city":["哈尔滨","大庆"]
- }
- },
- {
- "name":"广东",
- "cities":{
- "city":["广州","深圳","珠海"]
- }
- },
- {
- "name":"台湾",
- "cities":{
- "city":["台北","高雄"]
- }
- },
- {
- "name":"新疆",
- "cities":{
- "city":["乌鲁木齐"]
- }
- }
- ]
- }
那篇文章接下来部分是 .net 语言,不写这个,有兴趣大家移步去看吧,下面是 PHP 对 JSON 的处理:
PHP提供的专门的函数来生成和解析JSON格式的数据,PHP解析出来的数据根原先Javascript的数据的意义一样,即Javascript对象解析成PHP对象,Javascript数组解析成PHP数组,PHP应用JSON的函数是:json_encode($PHPcode);
PHP解析JSON的函数是:json_decode($JSONcode);
所以JSON的形式有多种,不同的形式在PHP解释出来后的形式也是不同的。
- //形式1:完全是对象的形式,这种形式的数据在Javascript中又叫相关数组,
- //与一般数组不同的是,它可以通过字符串作索引来访问(用“[]”或“.”来表示层级)
- $json='{"item1":{"item11":{"n":"chenling","m":"llll"},"sex":"男","age":"25"},
- "item2":{"item21":"ling","sex":"女","age":"24"}}';
- $J=json_decode($json);
- print_r($J);
将输出:
- stdClass Object
- (
- [item1] => stdClass Object
- (
- [item11] => stdClass Object
- (
- [n] => chenling
- [m] => llll
- )
- [sex] => 男
- [age] => 25
- )
- [item2] => stdClass Object
- (
- [item21] => ling
- [sex] => 女
- [age] => 24
- )
- )
比如说我要取得了值是chenling的那个属性,则应该这样访问:
- $J->item1->item11->n;//这将取得属性n的值:chenling
其实这种访问形式跟访问普通的对象属性差不多,也相当于访问一个3维数组。
- //形式2:对象和数组混合
- $json='{"item1":[{"name":[{"chen":"chenling","ling":"chenli"}],"sex":"男","age":"25"},
- {"name":"sun","sex":"女","age":"24"}]}';
- $J=json_decode($json);
- print_r($J);
将输出:
- stdClass Object
- (
- [item1] => Array
- (
- [0] => stdClass Object
- (
- [name] => Array
- (
- [0] => stdClass Object
- (
- [chen] => chenling
- [ling] => chenli
- )
- )
- [sex] => 男
- [age] => 25
- )
- [1] => stdClass Object
- (
- [name] => sun
- [sex] => 女
- [age] => 24
- )
- )
- )
比如说我要取得了值是chenling的那个元素,则应该这样访问:
- $J->item1[0]->name[0]->chen;//这将取得元素chen的值:chenling
其实这种PHP应用JSON形式结合了对象和数组的访问方式,也相当于访问一个5维数组。
- //形式3:完全数组形式
- $json='[["item1","item11"],["n","chenling"],["m","llll"]]';
- $J=json_decode($json);
- print_r($J);
将输出:
- Array
- (
- [0] => Array
- (
- [0] => item1
- [1] => item11
- )
- [1] => Array
- (
- [0] => n
- [1] => chenling
- )
- [2] => Array
- (
- [0] => m
- [1] => llll
- )
- )
$J[0][1];//这将取得元素值chenling的那个元素
但是用这种方式有一个缺点,就是无法用字符串作为索引,只能用数字,用完全对象的形式可以解决这个问题,其实这种访问形式就是数组的访问方式,相当于访问一个2维数组。
PHP应用JSON小结:
从上面的PHP应用JSON例子可以看出JSON有点类似XML,也可以在PHP和Javascript之间传递带结构的数据,使用起来很方便。
需要注意的是每个属性和属性值都由引号""包括起来。
接下来是 Python 对 JSON 的解析:
jso官方说明参见:http://json.org/
Python操作json的标准api库参考:http://docs.python.org/library/json.html
对简单数据类型的encoding 和 decoding:
使用简单的json.dumps方法对简单数据类型进行编码,例如:
- import json
- obj = [[1,2,3],123,123.123,'abc',{'key1':(1,2,3),'key2':(4,5,6)}]
- encodedjson = json.dumps(obj)
- print repr(obj)
- print encodedjson
输出:
- [[1, 2, 3], 123, 123.123, 'abc', {'key2': (4, 5, 6), 'key1': (1, 2, 3)}]
- [[1, 2, 3], 123, 123.123, "abc", {"key2": [4, 5, 6], "key1": [1, 2, 3]}]
通过输出的结果可以看出,简单类型通过encode之后跟其原始的repr()输出结果非常相似,但是有些数据类型进行了改变,例如上例中的元组则转换为了列表。在json的编码过程中,会存在从python原始类型向json类型的转化过程,具体的转化对照如下:
json.dumps()方法返回了一个str对象encodedjson,我们接下来在对encodedjson进行decode,得到原始数据,需要使用的json.loads()函数:
- decodejson = json.loads(encodedjson)
- print type(decodejson)
- print decodejson[4]['key1']
- print decodejson
输出:
- <type 'list'>
- [1, 2, 3]
- [[1, 2, 3], 123, 123.123, u'abc', {u'key2': [4, 5, 6], u'key1': [1, 2, 3]}]
loads方法返回了原始的对象,但是仍然发生了一些数据类型的转化。比如,上例中‘abc’转化为了unicode类型。从json到python的类型转化对照如下:
json.dumps方法提供了很多好用的参数可供选择,比较常用的有sort_keys(对dict对象进行排序,我们知道默认dict是无序存放的),separators,indent等参数。
排序功能使得存储的数据更加有利于观察,也使得对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
- print d2
- print d3
- print d1==d2
- print d1==d3
输出:
- {"a": 123, "b": 789, "c": 456}
- {"a": 123, "c": 456, "b": 789}
- {"a": 123, "b": 789, "c": 456}
- False
- True
上例中,本来data1和data2数据应该是一样的,但是由于dict存储的无序特性,造成两者无法比较。因此两者可以通过排序后的结果进行存储就避免了数据比较不一致的情况发生,但是排序后再进行存储,系统必定要多做一些事情,也一定会因此造成一定的性能消耗,所以适当排序是很重要的。
indent参数是缩进的意思,它可以使得数据存储的格式变得更加优雅。
- data1 = {'b':789,'c':456,'a':123}
- d1 = json.dumps(data1,sort_keys=True,indent=4)
- print d1
输出:
- {
- "a": 123,
- "b": 789,
- "c": 456
- }
输出的数据被格式化之后,变得可读性更强,但是却是通过增加一些冗余的空白格来进行填充的。json主要是作为一种数据通信的格式存在的,而网络通信是很在乎数据的大小的,无用的空格会占据很多通信带宽,所以适当时候也要对数据进行压缩。separator参数可以起到这样的作用,该参数传递是一个元组,包含分割对象的字符串。
- 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=(',',':')))
输出:
- 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的话,则会比较优雅的过度。
- data = {'b':789,'c':456,(1,2):123}
- print json.dumps(data,skipkeys=True)
输出:
- {"c": 456, "b": 789}
处理自己的数据类型
json模块不仅可以处理普通的python内置类型,也可以处理我们自定义的数据类型,而往往处理自定义的对象是很常用的。
首先,我们定义一个类Person。
- 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
如果直接通过json.dumps方法对Person的实例进行处理的话,会报错,因为json无法支持这样的自动转化。通过上面所提到的json和python的类型转化对照表,可以发现,object类型是和dict相关联的,所以我们需要把我们自定义的类型转化为dict,然后再进行处理。这里,有两种方法可以使用。
方法一:自己写转化函数
- '''''
- Created on 2011-12-14
- @author: Peter
- '''
- import Person
- import json
- p = Person.Person('Peter',22)
- def object2dict(obj):
- #convert object to a dict
- d = {}
- d['__class__'] = obj.__class__.__name__
- d['__module__'] = obj.__module__
- d.update(obj.__dict__)
- return d
- def dict2object(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.encode('ascii'), value) for key, value in d.items()) #get args
- inst = class_(**args) #create new instance
- else:
- inst = d
- return inst
- d = object2dict(p)
- print d
- #{'age': 22, '__module__': 'Person', '__class__': 'Person', 'name': 'Peter'}
- o = dict2object(d)
- print type(o),o
- #<class 'Person.Person'> Person Object name : Peter , age : 22
- dump = json.dumps(p,default=object2dict)
- print dump
- #{"age": 22, "__module__": "Person", "__class__": "Person", "name": "Peter"}
- load = json.loads(dump,object_hook = dict2object)
- print load
- #Person Object name : Peter , age : 22
上面代码已经写的很清楚了,实质就是自定义object类型和dict类型进行转化。object2dict函数将对象模块名、类名以及__dict__存储在dict对象里,并返回。dict2object函数则是反解出模块名、类名、参数,创建新的对象并返回。在json.dumps 方法中增加default参数,该参数表示在转化过程中调用指定的函数,同样在decode过程中json.loads方法增加object_hook,指定转化函数。
方法二:继承JSONEncoder和JSONDecoder类,覆写相关方法
JSONEncoder类负责编码,主要是通过其default函数进行转化,我们可以override该方法。同理对于JSONDecoder。
- '''''
- Created on 2011-12-14
- @author: Peter
- '''
- import Person
- import json
- p = Person.Person('Peter',22)
- class MyEncoder(json.JSONEncoder):
- def default(self,obj):
- #convert object to a 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.dict2object)
- def dict2object(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.encode('ascii'), 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
对于JSONDecoder类方法,稍微有点不同,但是改写起来也不是很麻烦。看代码应该就比较清楚了。