JSON серіалізація в Python за допомогою serpy

7 хв. читання

Серіалізація – процес перетворення об'єктів складних типів даних (спеціально визначених класів, об'єктно-реляційних маперів, тощо) до власних типів, таким чином, щоб їх потім можна було легко перетворювати в JSON-списки.

У цій статті ми будемо використовувати компактну бібліотеку serpy, щоб показати як такі перетворення працюють. Потім ми інтегруємо цей код у веб-сервер на tornado, щоб продемонструвати, як ми можемо писати API, що повертає дані JSON.

Крок 1: Визначаємо тип даних

Припустимо, що ми працюємо над API, який повертає дані про людей: ID, ім'я, дату народження. Будемо вважати, що API має доступ до бази даних з інформацією про людей, а коли ми зробимо запит /person/42, то отримаємо JSON-список даних про особу з ідентифікатором 42.

І тому перш ніж почати, швидко визначимо тип даних.

class Person(object):
    def __init__(self, id, name, birth_date):
        self.id = id
        self.name = name
        self.birth_date = birth_date
 
    @property
    def sidekick(self):
        return sidekick_for(self)

Припустимо також, що тепер функція sidekick_for повертає sidekick (товариша) для певної особи (що є також об'єктом Person). Пізніше ми додамо правильне визначення функції.

JSON приймає тільки власні типи даних: цілі, строки, булеві значення тощо. Зрозуміло, що Python функція json.dumps з об'єктом Person працювати не буде. Нам потрібно сформувати таку структуру даних, яка буде використовувати тільки власні типи даних, перед тим як ми зможемо передати їх до функції кодування JSON.

Метод №1 – Прямий

Ми можемо додати функцію to_json до класу Person, що повертатиме словник з даними особи. Це буде виглядати так:

def to_json(self):
    return {
        'id': self.id,
        'name': self.name,
        'birth_date': self.birth_date.isoformat(),
        'sidekick': self.sidekick.to_json() if self.sidekick else None,
    }

Ми повертаємо значення атрибутів Person і, оскільки вони всі власних типів, то можна передати цей словник у функцію кодування і нарешті отримати список JSON. Виглядає добре!

JSON серіалізація в Python за допомогою serpy

Зверніть увагу на те, що нам потрібно викликати функцію isoformat над self.birthdate, щоб отримати рядки, оскільки об'єкт datetime в Python не є власним типом. Також треба зауважити, що ми рекурсивно викликаємо функцію to_json над self.sidekick, щоб отримати його JSON відображення. Якщо цього не зробити, то змінна буде об'єктом Person, який неможливо буде конвертувати прямо в JSON. Поки це працює, але є декілька нюансів. По-перше, ми не можемо визначити типи полів. Якщо деякий код буде працювати з цим відображенням у JSON і ми передамо булеве значення для id, то такий код буде працювати невірно. В ідеалі ми б хотіли обробляти такі випадки на етапі серіалізації. Додатково деякі випадки можуть потребувати, щоб повернені значення були різні за деяким контекстом. За приклад можна взяти веб застосунок, що створює чат-кімнати для спілкування двох і більше користувачів. В таких випадках кількість непрочитаних повідомлень в залежності від користувача буде різним в одній кімнаті.

Найпростішим рішенням таких проблем буде відокремлення визначення серіалізатора з оригінального визначення класу. І тут з'являється serpy. Якщо serpy у вас ще не встановлений, то наберіть у командному рядку pip install serpy==0.1.1. Подивімося як його можна використовувати.

Метод №2 Визначення серіалізатора

from serpy import Serializer, IntField, StrField, MethodField
 
 
class PersonSerializer(Serializer):
    id = IntField(required=True)
    name = StrField(required=True)
    birth_date = MethodField('serialize_birth_date')
    sidekick = MethodField('serialize_sidekick')
 
    def serialize_birth_date(self, person):
        return person.birth_date.isoformat()
 
    def serialize_sidekick(self, person):
        if not person.sidekick:
            return None
        return PersonSerializer(person.sidekick).data

Що тільки що трапилось? Ми визначили PersonSerializer, який є класом, що визначає як об'єкт Person має бути серіалізований. Тепер ми знаємо типи полів, а це означає, що булеве значення для id буде вважатися помилкою і не повертатиме значення, що також буде помилкою, оскільки id зазначено як необхідне поле.

І це ще не все. Ми також досягли розподілу відповідальності, перемістивши функцію to_json в окремий клас серіалізатора. У випадку, якщо доведеться провести якусь операцію з кодом у майбутньому, це виглядає фантастичним.

JSON серіалізація в Python за допомогою serpy

Складаємо все до купи

Напишемо маленький API-сервер в tornado (запустіть в командному рядку pip install tornado==4.5.1), який об'єднає в собі увесь код з цієї статті. Збережіть наступний код у файлі server.py у поточній директорії.

from datetime import datetime
 
from serpy import Serializer, IntField, StrField, MethodField
from tornado.escape import json_encode
from tornado.ioloop import IOLoop
from tornado.web import RequestHandler, Application
 
class Person(object):
    def __init__(self, id, name, birth_date):
        self.id = id
        self.name = name
        self.birth_date = birth_date
 
    @property
    def sidekick(self):
        return sidekick_for(self)
 
class PersonSerializer(Serializer):
    id = IntField(required=True)
    name = StrField(required=True)
    birth_date = MethodField('serialize_birth_date')
    sidekick = MethodField('serialize_sidekick')
 
    def serialize_birth_date(self, person):
        return person.birth_date.isoformat()
 
    def serialize_sidekick(self, person):
        if not person.sidekick:
            return None
        return PersonSerializer(person.sidekick).data
 
batman = Person(1, 'Batman', datetime(year=1980, month=1, day=1))
robin = Person(2, 'Robin', datetime(year=1980, month=1, day=1))
 
def sidekick_for(person):
    return robin if person == batman else None
 
class BatmanHandler(RequestHandler):
    def get(self):
        self.write(json_encode(PersonSerializer(batman).data))
 
if __name__ == '__main__':
    Application([(r'/batman', BatmanHandler)]).listen(8888)
    print('Listening on port 8888')
 
    IOLoop.current().start()

Запустіть програму, виконавши python server.py в командному рядку в поточній директорії. Python має запустити веб сервер з портом 8888. Відвідавши сторінку URL /batman, ви маєте отримати JSON відображення об'єкта Person. Це працює!

JSON серіалізація в Python за допомогою serpy

Висновки!

В цій статті ми дослідили як серіалізувати об'єкти Python в JSON. Важливо також знати, що серіалізація не обмежена JSON. Існує багато інших форматів даних (наприклад XML), які можуть бути корисними. В будь-якому випадку основна ідея залишається такою ж. Цікавим завданням буде спробувати сдампити дані в різні формати та спробувати інші бібліотеки (наприклад, marshmallow). Щасливої серіалізації!

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 5.8K
Приєднався: 8 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація