Автор оригинала: Doug Hellmann.
Цель:
Кодировать объекты Python как строки JSON и декодировать JSON строки в объекты Python.
Модуль json
предоставляет API, аналогичный pickle, для преобразования объектов Python в памяти в сериализованное представление, известное как нотация объектов JavaScript (JSON). В отличие от pickle, JSON имеет то преимущество, что он реализован на многих языках (особенно JavaScript). Он наиболее широко используется для связи между веб-сервером и клиентом в REST API, но также полезен для других нужд взаимодействия между приложениями.
Кодирование и декодирование простых типов данных
Кодировщик понимает собственные типы 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 ()
.
$ python3 json_simple_types.py DATA: [{'a': 'A', 'b': (2, 4), 'c': 3.0}] JSON: [{"a": "A", "b": [2, 4], "c": 3.0}]
Кодирование с последующим повторным декодированием может не дать точно такой же тип объекта.
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']))
В частности, кортежи становятся списками.
$ python3 json_simple_types_decode.py DATA : [{'a': 'A', 'b': (2, 4), 'c': 3.0}] ENCODED: [{"a": "A", "b": [2, 4], "c": 3.0}] DECODED: [{'a': 'A', 'b': [2, 4], 'c': 3.0}] ORIGINAL:DECODED :
Расходные материалы для людей и компактный вывод
Еще одно преимущество JSON перед pickle заключается в том, что результаты читаются человеком. Функция dumps ()
принимает несколько аргументов, чтобы сделать вывод еще лучше. Например, флаг sort_keys
указывает кодировщику выводить ключи словаря в отсортированном, а не в случайном порядке.
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_keysTrue)) first json.dumps(data, sort_keysTrue) second json.dumps(data, sort_keysTrue) print('UNSORTED MATCH:', unsorted first) print('SORTED MATCH :', first second)
Сортировка упрощает просмотр результатов на глаз, а также дает возможность сравнивать вывод JSON в тестах.
$ python3 json_sort_keys.py DATA: [{'a': 'A', 'b': (2, 4), 'c': 3.0}] JSON: [{"a": "A", "b": [2, 4], "c": 3.0}] SORT: [{"a": "A", "b": [2, 4], "c": 3.0}] UNSORTED MATCH: True SORTED MATCH : True
Для структур данных с высокой степенью вложенности укажите значение для indent
, чтобы выходные данные также были правильно отформатированы.
json_indent.py
import json data [{'a': 'A', 'b': (2, 4), 'c': 3.0}] print('DATA:', repr(data)) print('NORMAL:', json.dumps(data, sort_keysTrue)) print('INDENT:', json.dumps(data, sort_keysTrue, indent2))
Когда indent является неотрицательным целым числом, вывод более похож на вывод pprint, с ведущими пробелами для каждого уровня структуры данных, соответствующими уровню отступа.
$ python3 json_indent.py 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 } ]
Однако такой подробный вывод увеличивает количество байтов, необходимых для передачи того же объема данных, поэтому он не предназначен для использования в производственной среде. Фактически, можно настроить параметры разделения данных в закодированном выводе, чтобы сделать его еще более компактным, чем по умолчанию.
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, indent2) print('dumps(data,, len(small_indent)) with_separators json.dumps(data, separators(',', ':')) print('dumps(data, separators):', len(with_separators))
Аргумент separators
для dumps ()
должен быть кортежем, содержащим строки для разделения элементов в списке и ключей от значений в словаре. По умолчанию используется (',', ':')
. Удалив пробелы, получается более компактный вывод.
$ python3 json_compact_encoding.py DATA: [{'a': 'A', 'b': (2, 4), 'c': 3.0}] repr(data) : 35 dumps(data) : 35 dumps(data, 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, skipkeysTrue))
Вместо того, чтобы вызывать исключение, нестроковый ключ игнорируется.
$ python3 json_skipkeys.py First attempt ERROR: keys must be str, int, float, bool or None, not tuple Second attempt [{"a": "A", "b": [2, 4], "c": 3.0}]
Работа с пользовательскими типами
До сих пор во всех примерах использовались встроенные типы Pythons, поскольку они изначально поддерживаются json
. Также часто требуется кодировать пользовательские классы, и есть два способа сделать это.
Учитывая этот класс для кодирования:
json_myobj.py
class MyObj: def __init__(self, s): self.s s def __repr__(self): return ''.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, defaultconvert_to_builtin_type))
В convert_to_builtin_type ()
экземпляры классов, не распознаваемых json
, преобразуются в словари с достаточной информацией для воссоздания объекта, если программа имеет доступ к необходимым модулям Python.
$ python3 json_dump_default.py First attempt ERROR: Object of type MyObj is not JSON serializable With default default() {"__class__": "MyObj", "__module__": "json_myobj", "s": "instance value goes here"}
Чтобы декодировать результаты и создать экземпляр MyObj ()
, используйте аргумент object_hook
для load ()
, чтобы привязать его к декодеру, чтобы класс можно импортировать из модуля и использовать для создания экземпляра.
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_hookdict_to_object, ) print(myobj_instance)
Поскольку json
преобразует строковые значения в объекты Unicode, их необходимо повторно закодировать как строки ASCII, прежде чем их можно будет использовать в качестве аргументов ключевого слова для конструктора класса.
$ python3 json_load_object_hook.py MODULE: json_myobj CLASS:INSTANCE ARGS: {'s': 'instance value goes here'} [ ]
Подобные ловушки доступны для встроенных типов целых чисел ( parse_int
), чисел с плавающей запятой ( parse_float
) и констант ( 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: "a" PART: : PART: "A" PART: , PART: "b" PART: : PART: [2 PART: , 4 PART: ] PART: , PART: "c" PART: : PART: 3.0 PART: } PART: ]
Метод encode ()
в основном эквивалентен '' .join (encoder.iterencode ())
с некоторой дополнительной проверкой ошибок заранее.
Чтобы кодировать произвольные объекты, замените метод default ()
реализацией, аналогичной той, которая используется в convert_to_builtin_type ()
.
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.pydefault( ) {"__class__": "MyObj", "__module__": "json_myobj", "s": "internal data"}
Декодирование текста, а затем преобразование словаря в объект требует немного больше работы по настройке, чем предыдущая реализация, но не намного.
json_decoder_object_hook.py
import json class MyDecoder(json.JSONDecoder): def __init__(self): json.JSONDecoder.__init__( self, object_hookself.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:INSTANCE ARGS: {'s': '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 [{"a": "A", "b": [2, 4], "c": 3.0}]
Хотя функция 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 [{'a': 'A', 'c': 3.0, 'b': [2, 4]}]
Смешанные потоки данных
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 : [{'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 в командной строке
Модуль 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 } ]
Смотрите также
- стандартная библиотечная документация для json
- Заметки о переносе Python 2 на 3 для json
- Нотация объектов JavaScript – домашняя страница JSON с документацией и реализациями на других языках.
- jsonpickle –
jsonpickle
позволяет сериализовать любой объект Python в JSON.