每周一個(gè) Python 模塊 | json

目的: 將 Python 對(duì)象編碼為 JSON 字符串,并將 JSON 字符串解碼為 Python 對(duì)象。

json 模塊提供了一個(gè)類似于 pickle 的 API,將內(nèi)存中的 Python 對(duì)象轉(zhuǎn)換為 JSON 序列。與 pickle 不同,JSON 具有以多種語言(尤其是 JavaScript)實(shí)現(xiàn)的優(yōu)點(diǎn)。它在 REST API 中 Web 服務(wù)端和客戶端之間的通信被廣泛應(yīng)用,同時(shí)對(duì)于應(yīng)用程序間通信需求也很有用。

編碼和解碼簡(jiǎn)單數(shù)據(jù)類型

Python 的默認(rèn)原生類型(str,intfloat,listtuple,和dict)。

import json

data = [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
print('DATA:', repr(data))  # DATA: [{'a': 'A', 'b': (2, 4), 'c': 3.0}]

data_string = json.dumps(data)
print('JSON:', data_string) # JSON: [{"a": "A", "b": [2, 4], "c": 3.0}]

表面上看,類似于 Python repr() 的輸出。

編碼,然后重新解碼可能不會(huì)給出完全相同類型的對(duì)象。

import json

data = [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
print('DATA   :', data) # DATA   : [{'a': 'A', 'b': (2, 4), 'c': 3.0}]

data_string = json.dumps(data)
print('ENCODED:', data_string)  # ENCODED: [{"a": "A", "b": [2, 4], "c": 3.0}]

decoded = json.loads(data_string)
print('DECODED:', decoded)  # [{'a': 'A', 'b': [2, 4], 'c': 3.0}]

print('ORIGINAL:', type(data[0]['b']))  # ORIGINAL: <class 'tuple'>
print('DECODED :', type(decoded[0]['b']))   # DECODED : <class 'list'>

特別是,元組成為了列表。

格式化輸出

JSON 的結(jié)果是更易于閱讀的。dumps() 函數(shù)接受幾個(gè)參數(shù)以使輸出更易讀結(jié)果。例如,sort_keys 標(biāo)志告訴編碼器以排序而不是隨機(jī)順序輸出字典的鍵。

import json

data = [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
print('DATA:', repr(data))  # DATA: [{'a': 'A', 'b': (2, 4), 'c': 3.0}]

unsorted = json.dumps(data)
print('JSON:', json.dumps(data))    # JSON: [{"a": "A", "b": [2, 4], "c": 3.0}]
print('SORT:', json.dumps(data, sort_keys=True))    # SORT: [{"a": "A", "b": [2, 4], "c": 3.0}]

first = json.dumps(data, sort_keys=True)
second = json.dumps(data, sort_keys=True)

print('UNSORTED MATCH:', unsorted == first) # UNSORTED MATCH: True
print('SORTED MATCH  :', first == second)   # SORTED MATCH  : True

對(duì)于高度嵌套的數(shù)據(jù)結(jié)構(gòu),可以指定 indent 參數(shù)來格式化輸出。

import json

data = [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
print('DATA:', repr(data))

print('NORMAL:', json.dumps(data, sort_keys=True))
print('INDENT:', json.dumps(data, sort_keys=True, indent=2))

# output
# DATA: [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
# NORMAL: [{"a": "A", "b": [2, 4], "c": 3.0}]
# INDENT: [
#   {
#     "a": "A",
#     "b": [
#       2,
#       4
#     ],
#     "c": 3.0
#   }
# ]

當(dāng) indent 是非負(fù)整數(shù)時(shí),輸出接近于 pprint,與數(shù)據(jù)結(jié)構(gòu)的每個(gè)級(jí)別的前導(dǎo)空格匹配縮進(jìn)級(jí)別。

import json

data = [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
print('DATA:', repr(data))

print('repr(data)             :', len(repr(data)))

plain_dump = json.dumps(data)
print('dumps(data)            :', len(plain_dump))

small_indent = json.dumps(data, indent=2)
print('dumps(data, indent=2)  :', len(small_indent))

with_separators = json.dumps(data, separators=(',', ':'))
print('dumps(data, separators):', len(with_separators))

# output
# DATA: [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
# repr(data)             : 35
# dumps(data)            : 35
# dumps(data, indent=2)  : 73
# dumps(data, separators): 29

dumps()separators 參數(shù)是一個(gè)元組,可以分開列表中的元素和字典中的鍵值對(duì),默認(rèn)是 (', ', ': ')。通過移除空白,可以產(chǎn)生更緊湊的輸出。

編碼字典

JSON 格式要求字典的鍵是字符串,如果使用非字符串類型作為鍵對(duì)字典進(jìn)行編碼,會(huì)報(bào)錯(cuò) TypeError。解決該限制的一種方法是使用 skipkeys 參數(shù)告訴編碼器跳過非字符串鍵:

import json

data = [{'a': 'A', 'b': (2, 4), 'c': 3.0, ('d',): 'D tuple'}]

print('First attempt')
try:
    print(json.dumps(data))
except TypeError as err:
    print('ERROR:', err)

print()
print('Second attempt')
print(json.dumps(data, skipkeys=True))

# output
# First attempt
# ERROR: keys must be str, int, float, bool or None, not tuple
# 
# Second attempt
# [{"a": "A", "b": [2, 4], "c": 3.0}]

不會(huì)引發(fā)異常,而是忽略非字符串鍵。

使用自定義類型

目前為止,所有的例子都是用的 Python 的內(nèi)置類型,json 原生就支持它們。不過有時(shí)我們也想編碼一些自定義類,這里我們有兩種方式來實(shí)現(xiàn)它。

嘗試把下面的類編碼:

# json_myobj.py 
class MyObj:

    def __init__(self, s):
        self.s = s

    def __repr__(self):
        return '<MyObj({})>'.format(self.s)

編碼 MyObj 實(shí)例最簡(jiǎn)單的方法是定義一個(gè)函數(shù),把未知的類型轉(zhuǎn)換成已知類型。它不需要進(jìn)行編碼操作,它只是把一個(gè)對(duì)象轉(zhuǎn)換成另一個(gè)對(duì)象。

import json
import json_myobj

obj = json_myobj.MyObj('instance value goes here')

print('First attempt')
try:
    print(json.dumps(obj))
except TypeError as err:
    print('ERROR:', err)


def convert_to_builtin_type(obj):
    print('default(', repr(obj), ')')
    # Convert objects to a dictionary of their representation
    d = {
        '__class__': obj.__class__.__name__,
        '__module__': obj.__module__,
    }
    d.update(obj.__dict__)
    return d


print()
print('With default')
print(json.dumps(obj, default=convert_to_builtin_type))

# output
# First attempt
# ERROR: Object of type MyObj is not JSON serializable
# 
# With default
# default( <MyObj(instance value goes here)> )
# {"__class__": "MyObj", "__module__": "json_myobj", "s": "instance value goes here"}

convert_to_bulitin_type() 中不能被 json 識(shí)別的對(duì)象被轉(zhuǎn)換成攜帶其信息的字典,如果程序有必要訪問這個(gè) Python 的模塊,轉(zhuǎn)換后的信息足夠?qū)ζ溥M(jìn)行重建。

我們要想根據(jù)解碼結(jié)果重建 MyObj() 實(shí)例,需要使用 loads()object_hook 參數(shù),這樣在處理時(shí)就可以從模塊中導(dǎo)入并用此來創(chuàng)建實(shí)例。

數(shù)據(jù)流中的每個(gè)字典都會(huì)調(diào)用 object_hook,這樣就不會(huì)錯(cuò)過要轉(zhuǎn)換的字典。hook 函數(shù)處理后的結(jié)果應(yīng)是應(yīng)用程序想要的對(duì)象。

import json


def dict_to_object(d):
    if '__class__' in d:
        class_name = d.pop('__class__')
        module_name = d.pop('__module__')
        module = __import__(module_name)
        print('MODULE:', module.__name__)
        class_ = getattr(module, class_name)
        print('CLASS:', class_)
        args = {
            key: value
            for key, value in d.items()
        }
        print('INSTANCE ARGS:', args)
        inst = class_(**args)
    else:
        inst = d
    return inst


encoded_object = '''
    [{"s": "instance value goes here",
      "__module__": "json_myobj", "__class__": "MyObj"}]
    '''

myobj_instance = json.loads(
    encoded_object,
    object_hook=dict_to_object,
)
print(myobj_instance)

# output
# MODULE: json_myobj
# CLASS: <class 'json_myobj.MyObj'>
# INSTANCE ARGS: {'s': 'instance value goes here'}
# [<MyObj(instance value goes here)>]

由于 json 會(huì)將字符串轉(zhuǎn)成 Unicode 對(duì)象,在把它們作為類構(gòu)造器的關(guān)鍵字參數(shù)之前我們還需要把它們重新編碼為 ASCII 。

類似的 hook 還能用在內(nèi)置類型整數(shù),浮點(diǎn)數(shù)和其他常量的轉(zhuǎn)換上。

編碼器和解碼器類

除了已經(jīng)涵蓋的簡(jiǎn)便函數(shù)外,json 模塊還提供用于解碼和編碼的類。使用類可以對(duì)自定義行為直接提供額外的 API。

JSONEncoder 使用的是可迭代的接口,可以生成編碼數(shù)據(jù)的「塊」,我們使用它可以更容易得將數(shù)據(jù)寫入文件或網(wǎng)絡(luò)套接字中而無需將整個(gè)數(shù)據(jù)結(jié)構(gòu)放到內(nèi)存中。

import json

encoder = json.JSONEncoder()
data = [{'a': 'A', 'b': (2, 4), 'c': 3.0}]

for part in encoder.iterencode(data):
    print('PART:', part)
    
# output
# PART: [
# PART: {
# PART: "a"
# PART: :
# PART: "A"
# PART: ,
# PART: "b"
# PART: :
# PART: [2
# PART: , 4
# PART: ]
# PART: ,
# PART: "c"
# PART: :
# PART: 3.0
# PART: }
# PART: ]

輸出以單個(gè)邏輯單位生成,不管之前是何種數(shù)據(jù)。

encode() 方法基本上等同于 ''.join(encoder.iterencode()),除了一些額外的錯(cuò)誤檢測(cè)。

要編碼任何想要編碼的對(duì)象,我們需要覆蓋 default() 方法并實(shí)現(xiàn)類似于 convert_to_bulitin_type() 功能的代碼。.

import json
import json_myobj


class MyEncoder(json.JSONEncoder):

    def default(self, obj):
        print('default(', repr(obj), ')')
        # Convert objects to a dictionary of their representation
        d = {
            '__class__': obj.__class__.__name__,
            '__module__': obj.__module__,
        }
        d.update(obj.__dict__)
        return d


obj = json_myobj.MyObj('internal data')
print(obj)
print(MyEncoder().encode(obj))

# output
# <MyObj(internal data)>
# default( <MyObj(internal data)> )
# {"__class__": "MyObj", "__module__": "json_myobj", "s": "internal data"}

輸出與先前的實(shí)現(xiàn)相同。

解碼文本,然后轉(zhuǎn)換字典到對(duì)象需要比之前稍多的步驟。

import json


class MyDecoder(json.JSONDecoder):

    def __init__(self):
        json.JSONDecoder.__init__(
            self,
            object_hook=self.dict_to_object,
        )

    def dict_to_object(self, d):
        if '__class__' in d:
            class_name = d.pop('__class__')
            module_name = d.pop('__module__')
            module = __import__(module_name)
            print('MODULE:', module.__name__)
            class_ = getattr(module, class_name)
            print('CLASS:', class_)
            args = {
                key: value
                for key, value in d.items()
            }
            print('INSTANCE ARGS:', args)
            inst = class_(**args)
        else:
            inst = d
        return inst


encoded_object = '''
[{"s": "instance value goes here",
  "__module__": "json_myobj", "__class__": "MyObj"}]
'''

myobj_instance = MyDecoder().decode(encoded_object)
print(myobj_instance)

# output
# MODULE: json_myobj
# CLASS: <class 'json_myobj.MyObj'>
# INSTANCE ARGS: {'s': 'instance value goes here'}
# [<MyObj(instance value goes here)>]

輸出與前面的例子相同。

使用流和文件

到目前為止,所有示例都假設(shè)整個(gè)數(shù)據(jù)結(jié)構(gòu)可以一次保存在內(nèi)存中。對(duì)于大型數(shù)據(jù)結(jié)構(gòu),最好將其直接寫入類文件對(duì)象。load()dump()接受對(duì)類似文件的對(duì)象的引用以用于讀取或?qū)懭搿?/p>

import io
import json

data = [{'a': 'A', 'b': (2, 4), 'c': 3.0}]

f = io.StringIO()
json.dump(data, f)

print(f.getvalue()) # [{"a": "A", "b": [2, 4], "c": 3.0}]

套接字或普通文件句柄的工作方式與本示例中使用的 StringIO 緩沖區(qū)相同 。

import io
import json

f = io.StringIO('[{"a": "A", "c": 3.0, "b": [2, 4]}]')
print(json.load(f)) # [{'a': 'A', 'c': 3.0, 'b': [2, 4]}]

就像 dump(),任何類似文件的對(duì)象都可以傳遞給 load()。

混合數(shù)據(jù)流

``JSONDecoder包含一個(gè)叫raw_decode()` 的方法,這個(gè)方法用于解碼跟在有結(jié)構(gòu)的數(shù)據(jù)之后的數(shù)據(jù),比如帶有尾文本的 JSON 數(shù)據(jù)。返回的值是由解碼后的輸入數(shù)據(jù)所創(chuàng)建的對(duì)象和解碼結(jié)束的位置的索引。

import json

decoder = json.JSONDecoder()


def get_decoded_and_remainder(input_data):
    obj, end = decoder.raw_decode(input_data)
    remaining = input_data[end:]
    return (obj, end, remaining)


encoded_object = '[{"a": "A", "c": 3.0, "b": [2, 4]}]'
extra_text = 'This text is not JSON.'

print('JSON first:')
data = ' '.join([encoded_object, extra_text])
obj, end, remaining = get_decoded_and_remainder(data)

print('Object              :', obj)
print('End of parsed input :', end)
print('Remaining text      :', repr(remaining))

print()
print('JSON embedded:')
try:
    data = ' '.join([extra_text, encoded_object, extra_text])
    obj, end, remaining = get_decoded_and_remainder(data)
except ValueError as err:
    print('ERROR:', err)
    
# output
# JSON first:
# Object              : [{'a': 'A', 'c': 3.0, 'b': [2, 4]}]
# End of parsed input : 35
# Remaining text      : ' This text is not JSON.'
# 
# JSON embedded:
# ERROR: Expecting value: line 1 column 1 (char 0)

不過,它只能在 JSON 對(duì)象在數(shù)據(jù)首部的時(shí)候才能正常工作,否則就會(huì)發(fā)生異常。

命令行中的 JSON

json.tool 模塊實(shí)現(xiàn)了一個(gè)命令行程序,用于重新格式化 JSON 數(shù)據(jù)以便于閱讀。

[{"a": "A", "c": 3.0, "b": [2, 4]}]

輸入文件 example.json 包含按字母順序排列的鍵映射。下面的第一個(gè)示例按順序顯示重新格式化的數(shù)據(jù),第二個(gè)示例用 --sort-keys,在打印輸出之前對(duì)映射鍵進(jìn)行排序。

$ python3 -m json.tool example.json

[
    {
        "a": "A",
        "c": 3.0,
        "b": [
            2,
            4
        ]
    }
]

$ python3 -m json.tool --sort-keys example.json

[
    {
        "a": "A",
        "b": [
            2,
            4
        ],
        "c": 3.0
    }
]

原文鏈接:

https://pymotw.com/3/json/index.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容