Загрузка данных из файла json

Как и в модуле pickle в модуле json есть функция load(), которая принимает на вход поток, читает из него данные в формате json и создает новый объект Python, который будет копией структуры данных записанной в json файле.

>>> shell
2
>>> del entry ①
>>> entry
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'entry' is not defined
>>> import json
>>> with open('entry.json', 'r', encoding='utf-8') as f:
... entry = json.load(f) ②
...
>>> entry ③
{'comments_link': None,
'internal_id': {'__class__': 'bytes', '__value__': [222, 213, 180, 248]},
'title': 'Dive into history, 2009 edition',
'tags': ['diveintopython', 'docbook', 'html'],
'article_link': 'http://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition',
'published_date': {'__class__': 'time.asctime', '__value__': 'Fri Mar 27 22:20:42 2009'},
'published': True}

① Для демонстрации переключитесь во вторую консоль Python и удалите структуру данных entry которую вы создали ранее в этой главе при помощи модуля Pickle.

② В простейшем случае функция json.load() работает так же как и функция pickle.load(). Вы передаете ей объект потока, а получаете в результате новый объект Python.

③ У меня есть хорошие и плохие новости. Хорошие новости в том, что функция json.load() успешно прочитала файл entry.json, который вы создали в первой консоли Python и создала новый объект Python, который содержит данные. А теперь плохие новости: она не воссоздала оригинальную структуру данных entry. Два значения 'internal_id' и 'published_date' были созданы как словари - а именно, как словари со значениями которые совместимы с json (именно их вы создали в функции преобразования to_json() )

Функция json.load() ничего не знает о функции преобразования, которую вы могли передать в json.dump(). Теперь вам нужно создать функцию, обратную to_json() — функцию, которая примет выборочно преобразованный json объект и преобразует его обратно в оригинальный объект Python.

# add this to customserializer.py
def from_json(json_object): ①
if '__class__' in json_object: ②
if json_object['__class__'] == 'time.asctime':
return time.strptime(json_object['__value__']) ③
if json_object['__class__'] == 'bytes':
return bytes(json_object['__value__']) ④
return json_object

① Эта функция преобразования так же принимает один параметр и возвращает одно значение. Но параметр который она принимает - не строка, это объект Python - результат десереализации строки JSON, в которую был преобразован объект Python.

② Все что вам нужно, так это проверить, содержит ли данный объект ключ '__class__', который создала функция to_json(). Если так, то значение найденное по этому ключу, расскажет вам, как декодировать этот объект обратно в оригинальный объект Python

③ Чтобы декодировать строку времени возвращаемую функцией time.asctime() вам нужно использовать функцию time.strptime(). Эта функция принимает параметром форматированную строку времени(в определяемом формате, но по умолчанию этот формат совпадает с форматом time.asctime()) и возвращает time.struct_time

④ Для преобразования списка чисел обратно в объекты bytes вы можете использовать функцию bytes()

Вот и все, всего два типа данных которые были обработаны функцией to_json() и теперь эти же типы данных были обработаны функцией from_json(). Вот результат:

>>> shell
2
>>> import customserializer
>>> with open('entry.json', 'r', encoding='utf-8') as f:
... entry = json.load(f, object_hook=customserializer.from_json) ①
...
>>> entry ②
{'comments_link': None,
'internal_id': b'\xDE\xD5\xB4\xF8',
'title': 'Dive into history, 2009 edition',
'tags': ['diveintopython', 'docbook', 'html'],
'article_link': 'http://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition',
'published_date': time.struct_time(tm_year=2009, tm_mon=3, tm_mday=27, tm_hour=22, tm_min=20, tm_sec=42, tm_wday=4, tm_yday=86, tm_isdst=-1),
'published': True}

① Чтобы встроить функцию from_json() в процесс десериализации, передайте ее в параметре object_hook в вызове функции json.load(). Функции которые принимают функции, как удобно!

② Структура данных entry теперь содержит ключ 'internal_id' со значением типа bytes. Также она содержит ключ 'published_date' со значением time.struct_time.

Хотя остался еще один глюк.

>>> shell
1
>>> import customserializer
>>> with open('entry.json', 'r', encoding='utf-8') as f:
... entry2 = json.load(f, object_hook=customserializer.from_json)
...
>>> entry2 == entry ①
False
>>> entry['tags'] ②
('diveintopython', 'docbook', 'html')
>>> entry2['tags'] ③
['diveintopython', 'docbook', 'html']

① Даже после встраивания функции to_json() в сериализацию и функции from_json() в десериализацию мы все еще не получили полную копию оригинальной структуры данных. Почему нет?

② В оригинальной структуре данных entry значение по ключу 'tags' было кортежем строк.

③ Но в воссозданной структуре данных entry2 значение по ключу 'tags' - это список строк. JSON не видит различий между кортежами и списками, в нем есть только один похожий на список тип данных, массив, и модуль json по-тихому преобразует и списки и кортежи в массивы json во время сериализации. Для большинства случаев, вы можете проигнорировать различие между списками и кортежами, но это нужно помнить, когда работаешь с модулем json.