Python 3 中的json模塊使用


1. 概述

JSON (JavaScript Object Notation)是一種使用廣泛的輕量數據格式. Python標准庫中的json模塊提供了JSON數據的處理功能.

Python中一種非常常用的基本數據結構就是字典(Dictionary). 它的典型結構如下:

d = {
    'a': 123,
    'b': {
        'x': ['A', 'B', 'C']
    }
}

而JSON的結構如下:

{
    "a": 123,
    "b": {
        "x": ["A", "B", "C"]
    }
}

可以看到, Dictionary和JSON非常接近, 而Python中的json庫提供的主要功能, 也是兩者之間的轉換.

2. 讀取JSON

json.loads方法可以將包含了一個JSON數據的str, bytes或者bytearray對象, 轉化為一個Python Dictionary. 它的完型接口簽名如下:

json.loads(s, *, encoding=None, cls=None, object_hook=None, parse_float=None, parse_int=None, parse_constant=None, object_pairs_hook=None, **kw)

2.1 最簡單的例子

json.loads最基本的使用方式就是將一個包含JSON數據的str傳遞給這個方法:

>>> json.loads('{"a": 123}')
{'a': 123}

注意

在Python中, str值可以放在一對單引號中, 也可以放在一對雙引號中:

>>> 'ABC' == "ABC"
True

所以, 在定義Dictionary的str類型的鍵和值的時候, 使用單引號或者雙引號都是合法和等價的:

>>> {"a": 'ABC'} == {'a': "ABC"}
True

但是, 在JSON中, 字符串數據只能放在雙引號中, 因而json.loads方法處理的字符串的JSON內容中, 字符串必須使用雙引號. 否則就會發生解碼錯誤:

>>> json.loads("{'a': 123}")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/__init__.py", line 354, in loads
    return _default_decoder.decode(s)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/decoder.py", line 339, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/decoder.py", line 355, in raw_decode
    obj, end = self.scan_once(s, idx)
json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)

如果被處理的Python字符串是包含在雙引號中的, 那么JSON中的雙引號就需要轉義:

>>> json.loads("{\"a\": 123}")
{'a': 123}

2.2 bytesbytearray數據

對於內容是JSON數據的bytesbytearray, json.loads方法也可以處理:

>>> json.loads('{"a": 123}'.encode('UTF-8'))
{'a': 123}
>>> json.loads(bytearray('{"a": 123}', 'UTF-8'))
{'a': 123}

2.3 編碼格式

json.loads的第二個參數是encoding沒有實際作用.

由於Python 3中str類型總是使用UTF-8編碼, 所以s參數為str類型時, json.loads方法自動使用UTF-8編碼. 並且, str不能以BOM字節開頭.

s參數為bytes或者bytearray時, json.loads方法會自動判斷為UTF-8, UTF-16還是UTF-32編碼. 默認也是將其按照UTF-8編碼轉化為str對象進行后續處理.

2.4 數據類型轉換

JSON可以表示四種主類型數據

  1. 字符串 string

  2. 數字 number

  3. 布爾類 boolean

  4. 空值 null

以及兩結數據結構

  1. 對象 object

  2. 數組 array

默認實現中, JSON和Python之間的數據轉換對應關系如下表:

JSON Python
object dict
array list
string str
number (int) int
number (real) float
true True
false False
null None

實際轉換情況如下例:

>>> json.loads("""
... {
...     "obj": {
...             "str": "ABC",
...             "int": 123,
...             "float": -321.89,
...             "bool_true": true,
...             "bool_false": false,
...             "null": null,
...             "array": [1, 2, 3]
...     }
... }""")
{'obj': {'str': 'ABC', 'int': 123, 'float': -321.89, 'bool_true': True, 'bool_false': False, 'null': None, 'array': [1, 2, 3]}}

對於JSON中數字number類型的數據, 有以下幾點需要注意:

  1. JSON中的實數real number類型的精度不能超過Python中的float類型的精度范圍, 否則就有精度損失. 如下例:

     >>> json.loads('3.141592653589793238462643383279')
     3.141592653589793
    
  2. JSON標准不包括非數字NaN, 正無窮Infinity和負無窮-Infinity, 但是json.loads方法默認會將JSON字符串中的NaN, Infinity, -Infinity轉化為Python中的float('nan'), float('inf')float('-inf'). 注意, 這里JSON中的NaN, Infinity, -Infinity必須大小寫正確並且拼寫完整. 如下例

     >>> json.loads('{"inf": Infinity, "nan": NaN, "ninf": -Infinity}')
     {'inf': inf, 'nan': nan, 'ninf': -inf}
    

2.5 自定義JSON對象轉換類型

json.loads默認將JSON中的對象數據轉化為Dictionary類型, object_hook參數可以用來改變構造出的對象.

object_hook接受一個函數, 這個函數的輸入參數為JSON中對象數據轉化出的Dictionary對象, 其返回值則為自定義的對象. 如下例所示:

>>> class MyJSONObj:
...     def __init__(self, x):
...             self.x = x
...
>>> def my_json_obj_hook(data):
...     print('obj_hook data: %s' % data)
...     return MyJSONObj(data['x'])
...
>>> result = json.loads('{"x": 123}', object_hook=my_json_obj_hook)
obj_hook data: {'x': 123}
>>> type(result)
<class '__main__.MyJSONObj'>
>>> result.x
123

當JSON中的對象有嵌套時, json.loads方法會按照深度優先的方式遍歷對象樹, 將各層的對象數據傳遞給object_hook. 葉節點的JSON對象構造出的Python對象, 會作為父節點的一個值, 傳遞給父節點的object_hook方法. 如下例:

>>> class MyJSONObj:
...     def __init__(self, x, y):
...             self.x = x
...             self.y = y
...
>>> def my_json_obj_hook(data):
...     print('obj_hook data: %s' % data)
...     return MyJSONObj(**data)
...
>>> result = json.loads('{"x": {"x": 11, "y": 12}, "y": {"x": 21, "y":22}}', object_hook=my_json_obj_hook)
obj_hook data: {'x': 11, 'y': 12}
obj_hook data: {'x': 21, 'y': 22}
obj_hook data: {'x': <__main__.MyJSONObj object at 0x10417ef28>, 'y': <__main__.MyJSONObj object at 0x10417ed68>}

除了object_hook參數以外, 還有一個object_pairs_hook參數. 這個參數同樣可以用來改變json.loads方法構造出的Python對象的類型. 這個參數和object_hook的不同, 在於傳入的方法所接收到的輸入數據不是一個Dictionary, 而是一個包含tuplelist. 每個tuple都有兩個元素, 第一個元素是JSON數據中的鍵, 第二個元素是這個鍵對應的值. 如JSON對象

{
    "a": 123,
    "b": "ABC"
}

對應的輸入數據是

[
    ('a': 123),
    ('b', 'ABC')
]

當調用json.loads方法時, 同時指定object_hookobject_pairs_hook, object_pairs_hook會覆蓋object_hook參數.

2.6 自定義JSON數字轉換類型

默認實現中, JSON中的實數被轉換為Python的float類型, 整數被轉換為int或者long類型. 類似object_hook, 我們可以通過parse_floatparse_int參數指定自定義的轉換邏輯. 這兩個方法的輸入參數為表示JSON實數或者整數的字符串. 下例中, 我們將實數轉換為numpy.float64, 將整數轉換為numpy.int64:

>>> def my_parse_float(f):
...     print('%s(%s)' % (type(f), f))
...     return numpy.float64(f)
...
>>> def my_parse_int(i):
...     print('%s(%s)' % (type(i), i))
...     return numpy.int64(i)
...
>>> result = json.loads('{"i": 123, "f": 321.45}', parse_float=my_parse_float, parse_int=my_parse_int)
<type 'str'>(123)
<type 'str'>(321.45)
>>> type(result['i'])
<type 'numpy.int64'>
>>> type(result['f'])
<type 'numpy.float64'>

2.6.1 自定義NaN, Infinity-Infinity轉換類型

由於標准JSON數據不支持NaN, Infinity-Infinity, 所以parse_float並不會接收到這幾個值. 當需要自定義這幾個值轉換的對象的時候, 就需要使用另外一個接口parse_constant. 比如下例中, 將這幾個值同樣轉換為numpy.float64類型:

>>> def my_parse_constant(data):
...     print('%s(%s)' % (type(data), data))
...     return numpy.float64(data)
...
>>> result = json.loads('{"inf": Infinity, "nan": NaN, "ninf": -Infinity}', parse_constant=my_parse_constant)
<type 'str'>(Infinity)
<type 'str'>(NaN)
<type 'str'>(-Infinity)
>>> result['inf']
inf
>>> type(result['inf'])
<type 'numpy.float64'>

2.7 非對象頂級值

根據JSON規范, 一個JSON數據中, 可以只包含一個值, 而不是一個完整的對象. 這個值可以是一個字符串, 一個數字, 布爾值, 空值, 或者一個數組. 除了這三種JSON規范中給出的類型, 還可以是NaN, Infinity或者-Infinity:

>>> json.loads('"hello"')
'hello'
>>> json.loads('123')
123
>>> json.loads('123.34')
123.34
>>> json.loads('true')
True
>>> json.loads('false')
False
>>> print(json.loads('null'))
None
>>> json.loads('[1, 2, 3]')
[1, 2, 3]

2.8 重復鍵名

在同一層級JSON對象中, 不應當出現重復的鍵名, 不過JSON規范中沒有給出這種情況的處理標准. 在json.loads中, 當JSON數據中有重復鍵名, 則后面的鍵值會覆蓋前面的:

>>> json.loads('{"a": 123, "b": "ABC", "a": 321}')
{'a': 321, 'b': 'ABC'}

2.9 處理JSON數據文件

當JSON數據是保存在一個文件中的時候, json.load方法可以用來從這個文件中讀取數據, 並轉換為Python對象. json.load方法的第一個參數就是指向JSON數據文件的文件類型對象.

比如/tmp/data.json文件的內含如下:

{
    "a": 123,
    "b": "ABC"
}

可以使用下例中的代碼來讀取並轉化文件中的JSON數據:

>>> with open('/tmp/data.json') as jf:
...     json.load(jf)
...
{u'a': 123, u'b': u'ABC'}

除了文件類型的對象, 只要是實現了read方法的類文件對象, 都可以作為fp參數, 比如下例中的io.StringIO:

>>> sio = io.StringIO('{"a": 123}')
>>> json.load(sio)
{'a': 123}

json.load方法的其他參數的意義和使用方法和上文中的json.loads相同, 這里不再贅述.

3 生成JSON

json.dumps方法可以將Python對象轉換為一個表示JONS數據的字符串. 它的完整接口簽名如下:

json.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)

它的第一個參數obj即為要轉換的數據對象.

>>> json.dumps({'a': 123, 'b': 'ABC'})
'{"a": 123, "b": "ABC"}'

3.1 編碼格式

json.dumpsensure_ascii參數用來控制生成的JSON字符串的編碼. 其默認值為True, 此時, 所有的非ASCII碼字條都會轉義. 如果不希望自動進行轉義, 則會保持原有編碼, 限UTF-8. 如下例所示:

>>> json.dumps({'數字': 123, '字符': '一二三'})
'{"\\u6570\\u5b57": 123, "\\u5b57\\u7b26": "\\u4e00\\u4e8c\\u4e09"}'
>>> json.dumps({'數字': 123, '字符': '一二三'}, ensure_ascii=False)
'{"數字": 123, "字符": "一二三"}'

3.2 數據類型轉換

在默認實現中, json.dumps可以處理的Python對象, 及其所有的屬性值, 類型必須為dict, list, tuple, str, float或者int. 這些類型與JSON的數據轉換關系如下表:

Python JSON
dict object
list, tuple array
str string
int, float, int-&float-derived emuns number
True true
False false
None null

實際轉換情況如下示例:

>>> json.dumps(
...     {
...             'str': 'ABC',
...             'int': 123,
...             'float': 321.45,
...             'bool_true': True,
...             'bool_false': False,
...             'none': None,
...             'list': [1, 2, 3],
...             'tuple': [12, 34]
...     }
... )
'{"str": "ABC", "int": 123, "float": 321.45, "bool_true": true, "bool_flase": false, "none": null, "list": [1, 2, 3], "tuple": [12, 34]}'

雖然JSON標准規范不支持NaN, Infinity-Infinity, 但是json.dumps的默認實現會將float('nan'), float('inf')float('-inf')轉換為常量NaN, Infinity, 和-Infinity. 如下例所示:

>>> json.dumps(
...     {
...             'nan': float('nan'),
...             'inf': float('inf'),
...             '-inf': float('-inf')
...     }
... )
'{"nan": NaN, "inf": Infinity, "-inf": -Infinity}'

由於這些常量可能會導致生成的JSON字符串不能被其他的JSON實現處理, 為了防止這種情況出現, 可以將json.dumpsallow_nan參數設置為True. 此時, 當處理的Python對象中出現這些值時, json.dumps方法會拋出異常.

3.3 循環引用

json.dumps方法會檢查Python對象中是否有循環引用, 如果發現了循環引用, 就會拋出異常. 如下例所示:

>>> circular_obj = {}
>>> circular_obj['self'] = circular_obj
>>> circular_obj
{'self': {...}}
>>> json.dumps(circular_obj)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/__init__.py", line 231, in dumps
    return _default_encoder.encode(obj)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/encoder.py", line 199, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/encoder.py", line 257, in iterencode
    return _iterencode(o, 0)
ValueError: Circular reference detected

如果不希望json.dumps方法檢查循環引用, 可以將參數check_circular設置為False. 但如果此時Python對象中有循環引用, 有可能發生遞歸嵌套過深的錯誤或者其他錯誤, 這么做是比較危險的. 如下例所示:

>>> json.dumps(circular_obj, check_circular=False)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/__init__.py", line 238, in dumps
    **kw).encode(obj)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/encoder.py", line 199, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/encoder.py", line 257, in iterencode
    return _iterencode(o, 0)
RecursionError: maximum recursion depth exceeded while encoding a JSON object

3.4 JSON字符串輸出格式

json.dumps方法的indent參數可以用來控制JSON字符串的換行和縮進效果.

indent參數默認值為None. 此時, JSON字符串不會有換行和縮進效果. 如下示:

>>> print(json.dumps({'a': 123, 'b': {'x': 321, 'y': 'ABC'}}))
{"a": 123, "b": {"x": 321, "y": "ABC"}}

indent為0或者負數時, JSON字符會包含換行:

>>> print(json.dumps({'a': 123, 'b': {'x': 321, 'y': 'ABC'}}, indent=-1))
{
"a": 123,
"b": {
"x": 321,
"y": "ABC"
}
}
>>> print(json.dumps({'a': 123, 'b': {'x': 321, 'y': 'ABC'}}, indent=0))
{
"a": 123,
"b": {
"x": 321,
"y": "ABC"
}
}

而當indent為正整數時, 除了換行, JSON還會以指定數量的空格為單位在對象層次間進行縮進:

>>> print(json.dumps({'a': 123, 'b': {'x': 321, 'y': 'ABC'}}, indent=2))
{
  "a": 123,
  "b": {
    "x": 321,
    "y": "ABC"
  }
}

indent還可以是str, 此時, JSON會以str內容為單位進行縮進, 比如制表符\t:

>>> print(json.dumps({'a': 123, 'b': {'x': 321, 'y': 'ABC'}}, indent='\t'))
{
    	"a": 123,
    	"b": {
	        "x": 321,
        	"y": "ABC"
    	}
}

json.dumps的另外一個參數separators可以用來設置輸出的分隔符. 這個參數的值應當是一個有兩個元素的tuple. 其第一個值為成員間的分隔符, 第二個值為鍵值之間的分隔符. 其默認值也會隨上文中的indent參數影響. 當indentNone時, separators的默認值為(', ', ': '), 即分隔符后都有一個空格. 當indent不為None時, 其默認值則為(',', ':'), 即只有鍵值間分隔符后會有一個空格, 而元素間分隔符則不帶空格, 因為此時會有換行.

separators參數的一種可能的使用場景是希望移除所有的非必要格式字符, 以此來減小JSON字符串的大小. 此時可以將separator設置為(',', ';'), 並不設置indent參數, 或者將其顯式設置為None:

>>> print(json.dumps({'a': 123, 'b': {'x': 321, 'y': 'ABC'}}, indent=None, separators=(',', ':')))
{"a":123,"b":{"x":321,"y":"ABC"}}

3.5 轉換自定義Python對象

json.dumps的默認實現只能轉換Dictionary類型的對象. 如果想要轉換自定義對象, 需要使用default參數. 這個參數接收一個函數, 這個函數的參數是一個要轉換的Python對象, 返回值是能夠表示這個Python對象的Dictionary對象. default函數會從對象引用樹的頂層開始, 逐層遍歷整個對象引用樹. 因此, 不用自己實現對象樹的遍歷邏輯, 只需要處理當前層次的對象. 如下例所示:

>>> class MyClass:
...     def __init__(self, x, y):
...             self.x = x
...             self.y = y
...
>>> def my_default(o):
...     if isinstance(o, MyClass):
...             print('%s.y: %s' % (type(o), o.y))
...             return {'x': o.x, 'y': o.y}
...     print(o)
...     return o
...
>>> obj = MyClass(x=MyClass(x=1, y=2), y=11)
>>> json.dumps(obj, default=my_default)
<class '__main__.MyClass'>.y: 11
<class '__main__.MyClass'>.y: 2
'{"x": {"x": 1, "y": 2}, "y": 11}'

3.6 非字符串類型鍵名

在Python中, 只是可哈希(hashable)的對象和數據都可以做為Dictionary對象的鍵, 而JSON規范中則只能使用字符串做為鍵名. 所以在json.dumps的實現中, 對這個規則進行了檢查, 不過鍵名允許的范圍有所擴大, str, int, float, boolNone類型的數據都可以做為鍵名. 不過當鍵名非str的情況時, 鍵名會轉換為對應的str值. 如下例:

>>> json.dumps(
...     {
...             'str': 'str',
...             123: 123,
...             321.54: 321.54,
...             True: True,
...             False: False,
...             None: None
...     }
... )
'{"str": "str", "123": 123, "321.54": 321.54, "true": true, "false": false, "null": null}'

而當出現其他類型的鍵名時, 默認出拋出異常:

>>> json.dumps({(1,2): 123})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/__init__.py", line 231, in dumps
    return _default_encoder.encode(obj)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/encoder.py", line 199, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/encoder.py", line 257, in iterencode
    return _iterencode(o, 0)
TypeError: keys must be a string

json.dumpsskipkeys參數可以改變這個行為. 當將skipkeys設置為True時, 遇到非法的鍵名類型, 不會拋出異常, 而是跳過這個鍵名:

>>> json.dumps({(1,2): 123}, skipkeys=True)
'{}'

3.7 生成JSON文件

當需要將生成的JSON數據保存到文件時, 可以使用json.dump方法. 這個方法比json.dumps多了一個參數fp, 這個參數就是用來保存JSON數據的文件對象. 比如, 下例中的代碼

>>> with open('/tmp/data.json', mode='a') as jf:
...     json.dump({'a': 123}, jf)
...

就會將JSON數據寫入到/tmp/data.json文件里. 代碼執行完后, 文件內容為

{"a": 123}

json.dump方法也可以接受其他類文件對象:

>>> sio = io.StringIO()
>>> json.dump({'a': 123}, sio)
>>> sio.getvalue()
'{"a": 123}'

json.dump的其他參數和json.dumps的用法相同, 這里不再贅述.

4 SON解碼和編碼類實現

json.loads, json.load, json.dumpsjson.dump這四個方法是通過json.JSONDecoderjson.JSONEncoder這兩個類來完成各自的任務的. 所以也可以直接使用這兩個類來完成前文描述的功能:

>>> json.JSONDecoder().decode('{"a": 123}')
{'a': 123}
>>> json.JSONEncoder().encode({'a': 123})
'{"a": 123}'

json.loads, json.load, json.dumpsjson.dump這個四個方法的參數主要都是傳遞給了json.JSONDecoderjson.JSONEncoder的構造方法, 所以使用這些方法可以滿足絕大部分需求. 當需要自定義json.JSONDecoderjson.JSONEncoder子類的時候, 只需要將子類傳遞給cls參數. 同時, 這些方法都有**kw參數. 當自定義實現類的構造函數需要標准參數列表之外的新參數時, 這個參數就會將新參數傳遞給實現類的構造方法.

5 相關資源


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM