Шаг 102.
Python: сборник рецептов.
Кодирование и обработка данных. Чтение и запись в формате JSON

    На этом шаге мы рассмотрим основные возможности модуля json.

Задача

    Вы хотите прочитать или записать данные, закодированные в JSON (JavaScript Object Notation).

Решение

    Модуль json предоставляет простой способ кодировать и декодировать данные в JSON. Две главные функции - json.dumps() и json.loads() - соответствуют интерфейсу других библиотек для сериализации, таких как pickle. Вот как вы можете превратить структуру данных Python в JSON:

import json

data = {
    'name': 'ACME',
    'shares': 100,
    'price': 542.23
}

json_str = json.dumps(data)

    А вот как можно превратить строку в JSON обратно в структуру данных Python:

#  Запись JSON-данных
with open('data.json', 'w') as f: 
    json.dump(data, f)

#  Чтение данных
with open('data.json', 'r') as f: 
    data = json.load(f)


Обсуждение

    Кодирование в JSON поддерживает базовые типы: None, bool, int, float, str, - а также списки, кортежи и словари, содержащие эти типы. В случае словарей ключами должны быть строки (все нестроковые ключи будут преобразованы в строки во время кодирования). Чтобы соответствовать спецификации JSON, вы должны кодировать только списки и словари Python. Более того, для веб-приложений стандартной практикой является использование именно словарей в качестве объектов верхнего уровня.

    Формат JSON практически идентичен синтаксису Python, за исключением нескольких небольших изменений. Например, True отображается на true, False - на false, а None - на null. Вот пример того, как выглядят закодированные данные:

>>> import json
>>> json.dumps(False)
'false'
>>> d = {'a': True, 'b': 'Hello', 'c': None} 
>>> json.dumps(d)
'{"a": true, "b": "Hello", "c": null}'
>>> 

    Если вы хотите изучить данные, которые вы раскодировали из JSON, часто бывает трудно установить их структуру путем простого вывода, особенно если присутствует многоуровневая вложенность или большое количество различных полей. Чтобы справиться с этой задачей, попробуйте функцию pprint() из модуля pprint. Она расположит ключи в алфавитном порядке и выведет словарь в более понятном виде. Вот пример того, как вы можете симпатично вывести результаты поиска по Twitter:

>>> from urllib.request import urlopen
>>> import json
>>> u = urlopen('http://search.twitter.com/search.json?q=python&rpp=5')
>>> resp = json.loads(u.read().decode('utf-8'))
>>> from pprint import pprint
>>> pprint(resp)
{'completed_in': 0.074,
'max_id': 264043230692245504,
'max_id_str': '264043230692245504',
'next_page': '?page=2&max_id=264043230692245504&q=python&rpp=5',
'page': 1,
'query': 'python',
'refresh_url': '?since_id=264043230692245504&q=python',
'results': [{'created_at': 'Thu, 01 Nov 2012 16:36:26 +0000', 'from_user': ...},
  {'created_at': 'Thu, 01 Nov 2012 16:36:14 +0000', 'from_user': ...},
  {'created_at': 'Thu, 01 Nov 2012 16:36:13 +0000', 'from_user': ...},
  {'created_at': 'Thu, 01 Nov 2012 16:36:07 +0000', 'from_user': ...}
  {'created_at': 'Thu, 01 Nov 2012 16:36:04 +0000', 'from_user': ...}],
'results_per_page': 5,
'since_id': 0,
'since_id_str': '0'}
>>>

    В обычном случае декодирование JSON создаст из предоставленных данных словари или списки. Если вы хотите создать другие объекты, передайте objects_ pair_hook или object_hook функции json.loads(). Например, вы можете декодировать JSON-данные, сохраняя их порядок в OrderedDict:

>>> s = '{"name": "ACME", "shares": 50, "price": 490.1}'
>>> from collections import OrderedDict
>>> data = json.loads(s, object_pairs_hook=OrderedDict)
>>> data
OrderedDict([('name', 'ACME'), ('shares', 50), ('price', 490.1)])
>>> 

    Вот как вы можете превратить словарь JSON в объект Python:

>>> class JSONObject:
	def __init__(self, d):
		self.__dict__ = d

		
>>> data = json.loads(s, object_hook=JSONObject)
>>> data.name
'ACME'
>>> data.shares
50
>>> data.price
490.1
>>>

    В последнем примере созданный при декодировании JSON-данных словарь передается как единственный аргумент в __init__(). Далее вы можете использовать его, как хотите, в том числе и напрямую в качестве экземпляра словаря объекта.

    Есть несколько параметров, которые могут быть полезны при кодировании в JSON. Если вы хотите, чтобы вывод был симпатично отформатирован, то можете использовать аргумент indent функции json.dumps(). В этом случае вывод будет красиво выводиться - в формате, похожем на вывод фунции pprint(). Например:

>>> s = '{"name": "ACME", "shares": 50, "price": 490.1}'
>>> data = json.loads(s, object_pairs_hook=OrderedDict)
>>> print(json.dumps(data))
{"name": "ACME", "shares": 50, "price": 490.1}
>>> print(json.dumps(data, indent=4))
{
    "name": "ACME",
    "shares": 50,
    "price": 490.1
}
>>> 

    Если вы хотите, чтобы при выводе происходила сортировка ключей, используйте аргумент sortkeys:

>>> print(json.dumps(data, sort_keys=True))
{"name": "ACME", "price": 542.23, "shares": 100}
>>>

    Экземпляры в обычном случае не являются сериализуемыми. Например:

>>> class Point:
	def __init__(self, x, y):
		self.x = x
		self.y = y

		
>>> p = Point(2, 3)
>>> json.dumps(p)
Traceback (most recent call last):
  File "<pyshell#35>", line 1, in <module>
    json.dumps(p)
  File "C:\Python38\lib\json\__init__.py", line 231, in dumps
    return _default_encoder.encode(obj)
  File "C:\Python38\lib\json\encoder.py", line 199, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "C:\Python38\lib\json\encoder.py", line 257, in iterencode
    return _iterencode(o, 0)
  File "C:\Python38\lib\json\encoder.py", line 179, in default
    raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type Point is not JSON serializable
>>> 

    Если вы хотите сериализовать экземпляры, то можете предоставить функцию, которая принимает экземпляр на вход и возвращает словарь, который может быть сериализован. Например:

>>> def serialize_instance(obj):
	d = {'__classname__': type(obj).__name__}
	d.update(vars(obj))
	return d

>>> 

    Если вы хотите получить экземпляр обратно, то можете сделать это так:

>>> # Словарь отображения имен на известные классы
>>> classes = {
	'Point': Point
	}
>>> def unserialize_object(d):
	clsname = d.pop('__classname__', None)
	if clsname:
		cls = classes[clsname]
		obj = cls.__new__(cls) # Создание экземпляра без вызова __init__
		for key, value in d.items():
			setattr(obj, key, value)
			return obj
	else:
		return d

	
>>>

    Вот пример того, как используются эти функции:

>>> p = Point(2, 3)
>>> s = json.dumps(p, default=serialize_instance)
>>> s
'{"__classname__": "Point", "x": 2, "y": 3}'
>>> a = json.loads(s, object_hook=unserialize_object)
>>> a
<__main__.Point object at 0x0000021336A25AF0>
>>> a.x
2
>>> a.y
3

    В модуле json множество других возможностей для контроля низкоуровневой интерпретации чисел, специальных значений (таких как NaN) и т. п. Обратитесь к документации за подробностями.


https://docs.python.org/3/library/json.html.

    На следующем шаге мы рассмотрим парсинг простых XML-данных.




Предыдущий шаг Содержание Следующий шаг