Как использовать JSONField в моделях Django?

Ответ

JSONField в Django позволяет хранить структурированные данные в формате JSON непосредственно в поле модели базы данных. Это полезно для гибких схем, хранения полуструктурированных данных или метаданных, где нет необходимости в строгой реляционной структуре.

Доступность и особенности:

  • Django 3.1+: JSONField доступен для всех поддерживаемых баз данных (PostgreSQL, SQLite, MySQL, Oracle).
  • PostgreSQL: Использует нативный тип jsonb, обеспечивая мощные возможности запросов и индексирования на уровне БД.
  • Другие БД: Хранит JSON как текстовую строку, выполняя сериализацию/десериализацию на уровне Django ORM. Возможности запросов на уровне БД могут быть ограничены.
  • Django < 3.1: Для PostgreSQL использовался django.contrib.postgres.fields.JSONField.

Пример определения модели:

from django.db import models
# Для Django >= 3.1
from django.db.models import JSONField
# Для Django < 3.1 и PostgreSQL
# from django.contrib.postgres.fields import JSONField

class Product(models.Model):
    name = models.CharField(max_length=100)
    # Поле для хранения произвольных атрибутов товара в формате JSON
    attributes = JSONField(default=dict, blank=True, null=True)

    def __str__(self):
        return self.name

Основные возможности:

  • Автоматическая сериализация/десериализация: Python-объекты (словари, списки) автоматически преобразуются в JSON при сохранении и обратно при загрузке.
  • Валидация: Поддерживает валидацию JSON на уровне модели Django.
  • Расширенные lookup-ы для запросов: Позволяет выполнять сложные запросы к данным внутри JSON-поля, особенно эффективно с PostgreSQL.

Примеры запросов:

# Создание объекта с JSON-данными
product = Product.objects.create(
    name="Laptop",
    attributes={
        "color": "silver",
        "weight_kg": 1.8,
        "features": ["SSD", "Webcam"],
        "manufacturer": {"name": "TechCorp", "country": "USA"}
    }
)

# Поиск товаров, у которых есть атрибут 'color'
products_with_color = Product.objects.filter(attributes__has_key='color')

# Поиск товаров определенного цвета (для PostgreSQL)
silver_products = Product.objects.filter(attributes__color='silver')

# Поиск товаров с определенной функцией в списке (для PostgreSQL)
ssd_products = Product.objects.filter(attributes__features__contains=['SSD'])

# Поиск по вложенному полю (для PostgreSQL)
us_manufactured = Product.objects.filter(attributes__manufacturer__name='TechCorp')

Недостатки:

  • Потеря строгой схемы: Отсутствие жесткой схемы на уровне БД может усложнить поддержку и валидацию данных.
  • Производительность: Для не-PostgreSQL баз данных запросы к JSON-полям могут быть менее производительными, так как требуют полной десериализации или использования строковых операций.

Ответ 18+ 🔞

Ну вот, смотри, опять эти ваши JSONField'ы полезли, блядь. Сидишь такой, пишешь модельку, и тут — бац! — надо хранить какую-то хуйню, которая в нормальные колонки не лезет. То цвет товара, то вес, то ещё какая-то мартышлюшка вроде списка фич. Раньше бы пришлось заводить отдельную таблицу, связи там, миграции писать... А теперь, ёпта, просто запихнул всё в один словарь и впердолил в базу. Удобно, чё.

Этот JSONField, он как карман на джинсах: туда можно и ключи, и телефон, и забытую конфету в обёртке засунуть. Главное — не забыть, что туда засунул, а то потом ищешь-ищешь, а там пиздец.

Короче, что он умеет:

  • Для всех БД с Django 3.1+: Теперь не только для постгреса, а везде. Правда, в SQLite и MySQL он будет хранить JSON как текст, а не как нативный тип. Ну, то есть, как будто ты json.dumps() сделал и строку записал. Запросы по нему будут тормознее, особенно если данных овердохуища.
  • Для PostgreSQL (самый кайф): Там он использует тип jsonb. Это, блядь, мощь! Индексы по нему можно строить, искать по вложенным полям — всё быстро, как по маслу. Прям как будто у тебя эти поля отдельными колонками были.
  • Автоматическая магия: Ты ему словарь или список даёшь, а он сам всё в JSON превращает и обратно. Не надо руками json.loads() вызывать. Красота.

Вот смотри, как это выглядит в коде:

from django.db import models
from django.db.models import JSONField  # Импортируем, как все нормальные люди

class Product(models.Model):
    name = models.CharField(max_length=100)
    # А это наша помойка для всего, что в голову взбредёт
    attributes = JSONField(default=dict, blank=True, null=True)

    def __str__(self):
        return self.name

А теперь самое интересное — как в этой помойке копаться (запросы):

# Создаём товар и сразу пихаем в него всю хуйню
product = Product.objects.create(
    name="Ноутбук",
    attributes={
        "color": "серебристый",
        "weight_kg": 1.8,
        "features": ["SSD", "Камера", "Подсветка клавиатуры"],
        "manufacturer": {"name": "TechCorp", "country": "Китай, блядь"}
    }
)

# Ищем все товары, у которых вообще есть ключ 'color' в атрибутах
products_with_color = Product.objects.filter(attributes__has_key='color')

# Ищем все серебристые товары (работает хорошо в основном на PostgreSQL)
silver_products = Product.objects.filter(attributes__color='серебристый')

# Ищем все товары, у которых в списке features есть 'SSD' (тоже для постгреса)
ssd_products = Product.objects.filter(attributes__features__contains=['SSD'])

# Ищем по вложенному объекту (manufacturer.name)
techcorp_products = Product.objects.filter(attributes__manufacturer__name='TechCorp')

Но не всё так гладко, ёпта. Есть и подводные грабли:

  • Схемы нет, ага. Ты можешь в один объект записать {"color": "red"}, а в другой — {"размер": "XL", "пиздец": true}. База не ругнётся. А потом придёт новый разработчик, посмотрит на это и скажет: "Я откуда знаю, что там может быть? Пиздец, нахуй!" Валидацию придётся писать самому, на уровне формы или сериализатора.
  • Производительность — как повезёт. На постгресе с jsonb — летает. На MySQL, где JSON хранится как текст, каждый запрос с условием по полю — это, по сути, поиск по подстроке. Если записей много, можно попить чайку, пока результат придёт.

В общем, инструмент охуенный, когда нужна гибкость. Но как говорится, с большой силой приходит большая ответственность, блядь. Не превращай свою базу в свалку, а то потом сам в ней не разберёшься.