Работа с данными JSON в Python

Работа с данными JSON в Python

С момента своего создания JSON быстро стал стандартом де-факто для обмена информацией. Скорее всего, вы здесь, потому что вам нужно перенести некоторые данные отсюда туда. Возможно, вы собираете информацию через API или храните свои данные в база данных документов. Так или иначе, вы по уши в JSON, и у вас есть выход на Python.

К счастью, это довольно распространенная задача, и, как и в случае большинства обычных задач, Python делает ее почти отвратительно легкой. Не бойтесь, собратья Pythoneers и Pythonistas. Это будет бриз!

_ Итак, мы используем JSON для хранения и обмена данными? Да, вы получили это! Это не более чем стандартный формат, который сообщество использует для передачи данных. Имейте в виду, JSON - не единственный формат, доступный для такой работы, но XML и YAML, вероятно, являются только другие стоит упомянуть на одном дыхании. _

*Бесплатная загрузка PDF:* https://realpython.com/bonus/python-cheat-sheet-short/[Python 3 Шпаргалка]

A (очень) краткая история JSON

Не удивительно, что * J ava S Cript O Bject N * Otation был вдохновлен подмножеством языка программирования JavaScript, имеющим дело с литеральным синтаксисом объекта. У них есть nifty веб-сайт, который объясняет все это. Не беспокойтесь: JSON уже давно стал независимым от языка и существует как собственный стандарт its, поэтому, к счастью, мы можем избежать использования JavaScript ради этого обсуждения.

В конечном счете, сообщество в целом приняло JSON, потому что людям и машинам легко создавать и понимать.

Смотри, это JSON!

Приготовься. Я собираюсь показать вам немного реальной жизни JSON - точно так же, как вы могли бы увидеть в дикой природе. Это нормально: JSON должен быть доступен для чтения любому, кто использовал язык C-стиля, а Python - язык C-стиля …​ так что это вы!

{
    "firstName": "Jane",
    "lastName": "Doe",
    "hobbies": ["running", "sky diving", "singing"],
    "age": 35,
    "children": [
        {
            "firstName": "Alice",
            "age": 6
        },
        {
            "firstName": "Bob",
            "age": 8
        }
    ]
}

Как видите, JSON поддерживает примитивные типы, такие как строки и числа, а также вложенные списки и объекты.

_ Подождите, это похоже на словарь Python! Я знаю, верно? На данный момент это довольно универсальная система обозначений объектов, но я не думаю, что UON скатывается с языка так же хорошо. Не стесняйтесь обсуждать альтернативы в комментариях. _

Уф! Вы пережили свою первую встречу с каким-то диким JSON. Теперь вам просто нужно научиться приручать его.

Python поддерживает JSON изначально!

Python поставляется со встроенным пакетом https://docs.python.org/3/library/json.html [+ json +] для кодирования и декодирования данных JSON.

Просто подбросьте этого маленького парня вверху вашего файла:

import json

Немного словарный запас

Процесс кодирования JSON обычно называется сериализацией . Этот термин относится к преобразованию данных в серию байтов (следовательно, serial) для хранения или передачи по сети. Вы также можете услышать термин «маршалинг», но это https://stackoverflow.com/questions/770474/what-is-the-difference-between-serialization-and-marshaling в целом другом обсуждении]. Естественно, десериализация - это взаимный процесс декодирования данных, которые были сохранены или доставлены в стандарте JSON.

_ Хлоп! Это звучит довольно технически. Определенно. Но на самом деле все, о чем мы здесь говорим, это _reading и writing. Подумайте об этом так: encoding предназначен для записи данных на диск, а decoding предназначен для читания данных в память. __

Сериализация JSON

Что происходит после того, как компьютер обрабатывает много информации? Нужно взять дамп данных. Соответственно, библиотека + json + предоставляет метод + dump () + для записи данных в файлы. Существует также метод + dumps () + (произносится как «dump-s») для записи в строку Python.

Простые объекты Python переводятся в JSON в соответствии с довольно интуитивным преобразованием.

Python JSON

dict

object

list, tuple

array

str

string

int, long, float

number

True

true

False

false

None

null

Простой пример сериализации

Представьте, что вы работаете с объектом Python в памяти, который выглядит примерно так:

data = {
    "president": {
        "name": "Zaphod Beeblebrox",
        "species": "Betelgeusian"
    }
}

Крайне важно сохранить эту информацию на диске, поэтому ваша задача - записать ее в файл.

Используя менеджер контекста Python, вы можете создать файл с именем + data_file.json + и открыть его в режиме записи. (Файлы JSON обычно заканчиваются расширением + .json +.)

with open("data_file.json", "w") as write_file:
    json.dump(data, write_file)

Обратите внимание, что + dump () + принимает два позиционных аргумента: (1) объект данных, подлежащий сериализации, и (2) файлоподобный объект, в который будут записаны байты.

Или, если вы были так склонны продолжать использовать эти сериализованные данные JSON в своей программе, вы можете записать их в собственный объект Python + str +.

json_string = json.dumps(data)

Обратите внимание, что файлоподобный объект отсутствует, поскольку вы фактически не записываете на диск. Кроме этого, + dumps () + аналогично + dump () +.

Ура! Вы родили какого-то малыша JSON и готовы выпустить его в дикую природу, чтобы стать большим и сильным.

Некоторые полезные аргументы ключевого слова

Помните, что JSON должен легко читаться людьми, но читаемого синтаксиса недостаточно, если все это сжато. Кроме того, у вас, вероятно, другой стиль программирования, чем у меня, и вам может быть проще читать код, когда он отформатирован по вашему вкусу.

_ ПРИМЕЧАНИЕ: Оба метода + dump () + и + dumps () + используют одинаковые ключевые аргументы. _

Первый вариант, который большинство людей хотят изменить - это пробел. Вы можете использовать ключевой аргумент + indent +, чтобы указать размер отступа для вложенных структур. Проверьте разницу, используя + data +, который мы определили выше, и запустив в консоли следующие команды:

>>>

>>> json.dumps(data)
>>> json.dumps(data, indent=4)

Другой вариант форматирования - аргумент ключевого слова + separators +. По умолчанию это кортеж из двух строк-разделителей + (", ",": ") +, но общей альтернативой для компактного JSON является + (", ",": ") +. Посмотрите на образец JSON еще раз, чтобы увидеть, где эти разделители вступают в игру.

Есть и другие, например + sort_keys +, но я понятия не имею, что это делает. Вы можете найти полный список в docs, если вам интересно.

Десериализация JSON

Отлично, похоже, ты запечатлел в себе какой-то дикий JSON! Теперь пришло время привести его в форму. В библиотеке + json + вы найдете + load () + и + load () + для преобразования закодированных данных JSON в объекты Python.

Как и в случае с сериализацией, существует простая таблица преобразования для десериализации, хотя вы, вероятно, уже догадались, как она выглядит.

JSON Python

object

dict

array

list

string

str

number (int)

int

number (real)

float

true

True

false

False

null

None

Технически это преобразование не является идеальной обратной таблицей сериализации. В основном это означает, что если вы кодируете объект сейчас, а потом декодируете его позже, вы не сможете получить точно такой же объект обратно. Я предполагаю, что это немного похоже на телепортацию: разбейте мои молекулы здесь и соедините их там. Я все еще тот же человек?

В действительности, это скорее похоже на то, как один друг переводит что-то на японский, а другой - обратно на английский. В любом случае, простейшим примером будет кодирование + tuple + и получение + list + после декодирования, например, так:

>>>

>>> blackjack_hand = (8, "Q")
>>> encoded_hand = json.dumps(blackjack_hand)
>>> decoded_hand = json.loads(encoded_hand)

>>> blackjack_hand == decoded_hand
False
>>> type(blackjack_hand)
<class 'tuple'>
>>> type(decoded_hand)
<class 'list'>
>>> blackjack_hand == tuple(decoded_hand)
True

Простой пример десериализации

На этот раз представьте, что на диске хранятся некоторые данные, которыми вы хотели бы манипулировать в памяти. Вы по-прежнему будете использовать менеджер контекста, но на этот раз вы откроете существующий + data_file.json + в режиме чтения.

with open("data_file.json", "r") as read_file:
    data = json.load(read_file)

Здесь все довольно просто, но имейте в виду, что результат этого метода может вернуть любой из разрешенных типов данных из таблицы преобразования. Это важно только в том случае, если вы загружаете данные, которых раньше не видели. В большинстве случаев корневым объектом будет + dict + или + list +.

Если вы извлекли данные JSON из другой программы или иным образом получили строку данных в формате JSON в Python, вы можете легко десериализовать это с помощью + load () +, который естественным образом загружается из строки:

json_string = """
{
    "researcher": {
        "name": "Ford Prefect",
        "species": "Betelgeusian",
        "relatives": [
            {
                "name": "Zaphod Beeblebrox",
                "species": "Betelgeusian"
            }
        ]
    }
}
"""
data = json.loads(json_string)

Вуаля! Вы приручили дикий JSON, и теперь он под вашим контролем. Но то, что вы делаете с этой силой, зависит от вас. Вы можете кормить его, воспитывать и даже учить трюкам. Дело не в том, что я тебе не доверяю …​ но держу это на поводке, хорошо?

Пример из реального мира (вроде)

В качестве вводного примера вы будете использовать JSONPlaceholder, отличный источник поддельных данных JSON для практических целей.

Сначала создайте файл сценария с именем + scratch.py ​​+ или как хотите. Я не могу тебя остановить.

Вам нужно будет сделать запрос API к службе JSONPlaceholder, поэтому просто используйте пакет + запросы + для выполнения тяжелой работы. Добавьте этот импорт вверху вашего файла:

import json
import requests

Теперь вы будете работать со списком TODO, потому что …​ вы знаете, это обряд или что-то в этом роде.

Продолжайте и сделайте запрос к API JSONPlaceholder для конечной точки +/todos +. Если вы не знакомы с + reports +, на самом деле есть удобный метод + json () +, который сделает всю работу за вас, но вы можете попрактиковаться в использовании библиотеки + json + для десериализации `+ text + `атрибут объекта ответа. Это должно выглядеть примерно так:

response = requests.get("https://jsonplaceholder.typicode.com/todos")
todos = json.loads(response.text)

Вы не верите, что это работает? Хорошо, запустите файл в интерактивном режиме и проверьте его сами. Пока вы это делаете, проверьте тип + todos +. Если вы чувствуете себя авантюрным, взгляните на первые 10 или около того пунктов в списке.

>>>

>>> todos == response.json()
True
>>> type(todos)
<class 'list'>
>>> todos[:10]
...

Видишь, я бы не соврал тебе, но я рад, что ты скептик.

_ Что такое интерактивный режим? Ах, я думал, вы никогда не спросите! Вы знаете, как вы всегда прыгаете вперед и назад между вашим редактором и терминалом? Что ж, мы, хитрые Pythoneers, используем интерактивный флаг + -i +, когда запускаем скрипт. Это отличный маленький трюк для тестирования кода, потому что он запускает скрипт, а затем открывает интерактивную командную строку с доступом ко всем данным из скрипта! _

Хорошо, время для некоторых действий. Вы можете увидеть структуру данных, посетив endpoint в браузере, но вот пример TODO:

{
    "userId": 1,
    "id": 1,
    "title": "delectus aut autem",
    "completed": false
}

Есть несколько пользователей, каждый с уникальным + userId +, и у каждой задачи есть логическое свойство + complete +. Можете ли вы определить, какие пользователи выполнили больше всего задач?

# Map of userId to number of complete TODOs for that user
todos_by_user = {}

# Increment complete TODOs count for each user.
for todo in todos:
    if todo["completed"]:
        try:
            # Increment the existing user's count.
            todos_by_user[todo["userId"]] += 1
        except KeyError:
            # This user has not been seen. Set their count to 1.
            todos_by_user[todo["userId"]] = 1

# Create a sorted list of (userId, num_complete) pairs.
top_users = sorted(todos_by_user.items(),
                   key=lambda x: x[1], reverse=True)

# Get the maximum number of complete TODOs.
max_complete = top_users[0][1]

# Create a list of all users who have completed
# the maximum number of TODOs.
users = []
for user, num_complete in top_users:
    if num_complete < max_complete:
        break
    users.append(str(user))

max_users = " and ".join(users)

Да, да, ваша реализация лучше, но суть в том, что теперь вы можете манипулировать данными JSON как обычным объектом Python!

Я не знаю о вас, но когда я снова запускаю скрипт в интерактивном режиме, я получаю следующие результаты:

>>>

>>> s = "s" if len(users) > 1 else ""
>>> print(f"user{s} {max_users} completed {max_complete} TODOs")
users 5 and 10 completed 12 TODOs

Это круто и все, но вы здесь, чтобы узнать о JSON. Для окончательного задания вы создадите файл JSON, содержащий полные TODO для каждого из пользователей, которые выполнили максимальное количество TODO.

Все, что вам нужно сделать, это отфильтровать + todos + и записать полученный список в файл. Ради оригинальности вы можете вызвать выходной файл + Filter_Data_file.json +. Есть несколько способов сделать это, но вот один из них:

# Define a function to filter out completed TODOs
# of users with max completed TODOS.
def keep(todo):
    is_complete = todo["completed"]
    has_max_count = str(todo["userId"]) in users
    return is_complete and has_max_count

# Write filtered TODOs to file.
with open("filtered_data_file.json", "w") as data_file:
    filtered_todos = list(filter(keep, todos))
    json.dump(filtered_todos, data_file, indent=2)

Отлично, вы избавились от всех данных, которые вам не нужны, и сохранили полезные вещи в совершенно новый файл! Запустите сценарий еще раз и проверьте + Filter_data_file.json +, чтобы убедиться, что все работает. Он будет находиться в том же каталоге, что и + scratch.py ​​+, когда вы его запустите.

Теперь, когда вы продвинулись так далеко, держу пари, что вы чувствуете себя довольно жарко, верно? Не дерзайте: смирение - это добродетель. Я склонен согласиться с вами, хотя. Пока что плавание прошло гладко, но вы можете захлопнуть люки на этом последнем этапе пути.

Кодирование и декодирование пользовательских объектов Python

Что происходит, когда мы пытаемся сериализовать класс + Elf + из приложения Dungeons & Dragons, над которым вы работаете?

class Elf:
    def __init__(self, level, ability_scores=None):
        self.level = level
        self.ability_scores = {
            "str": 11, "dex": 12, "con": 10,
            "int": 16, "wis": 14, "cha": 13
        } if ability_scores is None else ability_scores
        self.hp = 10 + self.ability_scores["con"]

Неудивительно, что Python жалуется, что + Elf + не serializable (что вы бы знали, если бы когда-нибудь пытались сказать эльфу иначе):

>>>

>>> elf = Elf(level=4)
>>> json.dumps(elf)
TypeError: Object of type 'Elf' is not JSON serializable

Хотя модуль + json + может обрабатывать большинство встроенных типов Python, он не понимает, как кодировать пользовательские типы данных по умолчанию. Это все равно что пытаться втиснуть квадратный колышек в круглое отверстие - вам нужна гудение и родительский контроль.

Упрощение структур данных

Теперь вопрос в том, как бороться с более сложными структурами данных. Что ж, вы можете попробовать закодировать и декодировать JSON вручную, но есть немного более умное решение, которое сэкономит вам немного работы. Вместо прямого перехода от пользовательского типа данных к JSON вы можете добавить промежуточный шаг.

Все, что вам нужно сделать, это представить ваши данные в терминах встроенных типов, которые + json + уже понимает. По сути, вы переводите более сложный объект в более простое представление, которое модуль + json + затем переводит в JSON. Это как переходное свойство в математике: если A = B и B = C, то A = C.

Чтобы разобраться в этом, вам понадобится сложный объект для игры. Вы можете использовать любой пользовательский класс, который вам нравится, но в Python есть встроенный тип + complex + для представления комплексных чисел, и он не сериализуем по умолчанию. Итак, ради этих примеров, ваш сложный объект будет объектом + complex +. Смущены еще?

>>>

>>> z = 3 + 8j
>>> type(z)
<class 'complex'>
>>> json.dumps(z)
TypeError: Object of type 'complex' is not JSON serializable

_ Откуда берутся комплексные числа? Видите ли, когда действительное число и воображаемое число очень любят друг друга, они складываются вместе, образуя число, которое (оправданно) называется _complex