[譯] json — JavaScript 對象表示法

json — JavaScript 對象表示法

目的:實現 Python 對象和 JSON 字符串間的相互轉化。javascript

json 模塊提供了一個相似於 pickle 的 API,用於將內存中的 Python 對象轉換爲 JavaScript Object Notation(JSON)的序列化表示形式。相較於 pickle,JSON 優點之一就是被許多語言實現和應用(特別是 JavaScript)。它被普遍用於 REST API 中Web服務器和客戶端之間的通訊,此外也可用於其餘應用程序間的通訊。html

編碼和解碼簡單的數據類型

JSON 編碼器原生支持 Python 的基本類型 (str, int, float, list, tuple, 和 dict).前端

# json_simple_types.py

import json

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

data_string = json.dumps(data)
print('JSON:', data_string)
複製代碼

值的編碼方式看起來相似於 Python 的 repr() 輸出。java

$ python3 json_simple_types.py

DATA: [{'c': 3.0, 'b': (2, 4), 'a': 'A'}]
JSON: [{"c": 3.0, "b": [2, 4], "a": "A"}]
複製代碼

編碼以後再解碼,將可能獲得並不徹底相同的對象類型。python

# json_simple_types_decode.py

import json

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

data_string = json.dumps(data)
print('ENCODED:', data_string)

decoded = json.loads(data_string)
print('DECODED:', decoded)

print('ORIGINAL:', type(data[0]['b']))
print('DECODED :', type(decoded[0]['b']))
複製代碼

特別注意,元組會轉化成列表。android

$ python3 json_simple_types_decode.py

DATA   : [{'c': 3.0, 'b': (2, 4), 'a': 'A'}]
ENCODED: [{"c": 3.0, "b": [2, 4], "a": "A"}]
DECODED: [{'c': 3.0, 'b': [2, 4], 'a': 'A'}]
ORIGINAL: <class 'tuple'>
DECODED : <class 'list'>
複製代碼

可讀性 vs. 緊湊型輸出

相較於pickle,JSON 可讀性更好。 dumps() 函數接收若干參數來優化輸出的可讀性。例如,sort_keys參數告訴編碼器以排序而不是隨機順序輸出字典的鍵的值。ios

# json_sort_keys.py

import json

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

unsorted = json.dumps(data)
print('JSON:', json.dumps(data))
print('SORT:', json.dumps(data, sort_keys=True))

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

print('UNSORTED MATCH:', unsorted == first)
print('SORTED MATCH :', first == second)
複製代碼

有序輸出,可讀性天然比較高,而且在測試中容易對 JSON 的輸出進行比較。git

$ python3 json_sort_keys.py

DATA: [{'c': 3.0, 'b': (2, 4), 'a': 'A'}]
JSON: [{"c": 3.0, "b": [2, 4], "a": "A"}]
SORT: [{"a": "A", "b": [2, 4], "c": 3.0}]
UNSORTED MATCH: False
SORTED MATCH  : True
複製代碼

對於高度嵌套的數據結構,請爲 indent 指定一個值,以便輸出結構更加清晰的格式。github

# json_indent.py

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))
複製代碼

當 indent 是一個非負整數時,其輸出更接近 pprint 的輸出,其縮進的空格數與傳入的 indent 值相同,展現了清晰的數據結構。json

$ python3 json_indent.py

DATA: [{'c': 3.0, 'b': (2, 4), 'a': 'A'}]
NORMAL: [{"a": "A", "b": [2, 4], "c": 3.0}]
INDENT: [
  {
    "a": "A",
    "b": [
      2,
      4
    ],
    "c": 3.0
  }
]
複製代碼

雖然輸出的數據結構更清晰,但增長了傳輸相同數據量所需的字節數,所以它不適用於生產環境。實際上,能夠經過設置輸出的 separators 參數,使其編碼後的值比默認狀況下更緊湊。

# json_compact_encoding.py

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))
複製代碼

dumps()separators參數應該是一個包含字符串的元組,用於分隔列表中的項目以及字典中的值。默認值是'(',',':')`。經過消除空佔位符,生成更緊湊的輸出。

$ python3 json_compact_encoding.py

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

編碼字典

JSON 指望字典鍵的格式是字符串。若是用非字符串類型做爲鍵編碼字典會產生一個 TypeError。解決該限制的一種方法是使用 skipkeys 參數告訴編碼器跳過非字符串鍵:

# json_skipkeys.py

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))
複製代碼

沒有拋出異常,忽略了非字符串鍵。

$ python3 json_skipkeys.py

First attempt
ERROR: keys must be a string

Second attempt
[{"c": 3.0, "b": [2, 4], "a": "A"}]
複製代碼

使用自定義類型

到目前爲止,全部的例子都使用了 Pythons 的內置類型,由於這些類型自己就支持 json。此外,一般還須要對自定義類進行編碼,而且有兩種方法能夠作到這一點。

將下面的類進行編碼:

# json_myobj.py

class MyObj:

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

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

說個簡單編碼 MyObj 實例的方法:定義一個函數將未知類型轉換爲已知類型。類自己不須要進行編碼,因此它只是將一個對象轉換爲另外一個。

# json_dump_default.py

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))
複製代碼

依賴的模塊能夠正常訪問的狀況下,在 convert_to_builtin_type() 中,沒有被 json 識別的類的實例被格式化爲具備足夠信息的字典,而後從新建立該對象.

$ python3 json_dump_default.py

First attempt
ERROR: <MyObj(instance value goes here)> is not JSON serializable

With default
default( <MyObj(instance value goes here)> )
{"s": "instance value goes here", "__module__": "json_myobj",
"__class__": "MyObj"}
複製代碼

要解析結果並建立一個 MyObj()實例,可使用 object_hook 參數來調用 loads() 以鏈接解析器,這樣就能夠從模塊中導入類並用於建立實例。

爲從輸入數據流中解碼出的每一個字典調用 object_hook,它提供將字典轉換爲另外一種類型的對象的功能。鉤子函數應該返回調用應用程序應該接收的對象而不是字典

# json_load_object_hook.py

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)
複製代碼

因爲 json 將字符串值轉換爲了 unicode 對象,所以在將其用做類構造函數的關鍵字參數以前,須要將它們從新編碼爲 ASCII 字符串。

$ python3 json_load_object_hook.py

MODULE: json_myobj
CLASS: <class 'json_myobj.MyObj'>
INSTANCE ARGS: {'s': 'instance value goes here'}
[<MyObj(instance value goes here)>]
複製代碼

相似的鉤子可用於內置的數據類型:整數(parseint),浮點數(parsefloat)和常量(parse constant)。

編碼器和解析器相關的類

除了已經介紹的便利功能以外,json 模塊還提供了編碼和解析相關的類。直接使用類能夠訪問額外的 API 來定製它們的行爲。

JSONEncoder 使用一個可迭代的接口來產生編碼數據的 「塊」,使得它更容易寫入文件或網絡套接字,而無需在內存中表示整個數據結構。

# json_encoder_iterable.py

import json

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

for part in encoder.iterencode(data):
    print('PART:', part)
複製代碼

輸出以邏輯單位爲準,和值的大小無關。

$ python3 json_encoder_iterable.py

PART: [
PART: {
PART: "c"
PART: :
PART: 3.0
PART: ,
PART: "b"
PART: :
PART: [2
PART: , 4
PART: ]
PART: ,
PART: "a"
PART: :
PART: "A"
PART: }
PART: ]
複製代碼

encode() 方法基本上等同於 ''.join(encoder.iterencode()),此外還有一些預先錯誤檢查。

要對任意對象進行編碼,建議使用與 convert_to_builtin_type() 中使用的相似的實現來重載 default() 方法。

# json_encoder_default.py

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))
複製代碼

和以前的例子輸出相同。

$ python3 json_encoder_default.py

<MyObj(internal data)>
default( <MyObj(internal data)> )
{"s": "internal data", "__module__": "json_myobj", "__class__":
"MyObj"}
複製代碼

解析文本,將字典轉換爲對象比上面提到的實現方法更爲複雜,不過差異不大。

# json_decoder_object_hook.py

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)
複製代碼

輸出與前面的例子相同。

$ python3 json_decoder_object_hook.py

MODULE: json_myobj
CLASS: <class 'json_myobj.MyObj'>
INSTANCE ARGS: {'s': 'instance value goes here'}
[<MyObj(instance value goes here)>]
複製代碼

使用流和文件

到目前爲止,全部的例子的前提都是假設整個數據結構的編碼版本能夠一次保存在內存中。對於包含大量數據的複雜結構,將編碼直接寫入文件類對象會比較好。load()dump() 函數能夠接受文件類對象的引用做爲參數,來進行方便讀寫操做。

# json_dump_file.py

import io
import json

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

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

print(f.getvalue())
複製代碼

套接字或常規文件句柄有着和本示例中使用的 StringIO 緩衝區相同的工做方式。

$ python3 json_dump_file.py

[{"c": 3.0, "b": [2, 4], "a": "A"}]
複製代碼

儘管它沒有被優化爲一次只讀取一部分數據,但 load() 函數仍然提供了一種把輸入流轉換成對象的封裝邏輯方面的好處。

# json_load_file.py

import io
import json

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

就像 dump() 同樣,任何類文件對象均可以傳遞給 load()

$ python3 json_load_file.py

[{'c': 3.0, 'b': [2, 4], 'a': 'A'}]
複製代碼

混合數據流

JSONDecoder 包含 raw_decode(),這是一種解碼數據結構後面跟着更多數據的方法,好比帶有尾隨文本的 JSON 數據。返回值是經過對輸入數據進行解碼而建立的對象,以及指示解碼器在何處中止工做的位置索引。

# json_mixed_data.py

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)
複製代碼

可是,這隻有在對象出如今輸入的開頭時纔有效。

$ python3 json_mixed_data.py

JSON first:
Object              : [{'c': 3.0, 'b': [2, 4], 'a': 'A'}]
End of parsed input : 35
Remaining text      : ' This text is not JSON.'

JSON embedded:
ERROR: Expecting value: line 1 column 1 (char 0)
複製代碼

命令行中的 JSON

json.tool 模塊實現了一個命令行程序,用於從新格式化 JSON 數據以便於閱讀。

[{"a": "A", "c": 3.0, "b": [2, 4]}]
複製代碼

輸入文件 example.json 包含一個按字母順序排列的映射。下面的第一個例子顯示按順序從新格式化的數據,第二個例子使用 --sort-keys 在打印輸出以前對映射鍵進行排序。

$ 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
    }
]
複製代碼

參閱


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索