Pick и omit

Базовая идея композиции структур

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

В Superstruct подобная функциональность реализована через утилиты pick и omit, которые позволяют создавать производные структуры на основе уже существующих определений.

Основной принцип:

  • pick — выбор только указанных полей из структуры
  • omit — исключение указанных полей из структуры

Эти операции работают аналогично утилитам TypeScript Pick и Omit, но применяются к runtime-схемам.


Pick

Назначение

pick создаёт новую структуру, содержащую только явно перечисленные поля исходной схемы. Остальные поля игнорируются на уровне валидации.

Общая форма

const NewStruct = pick(OriginalStruct, ['fieldA', 'fieldB'])

Результат — структура, в которой валидируются только указанные ключи.


Пример базовой структуры

import { object, string, number } from 'superstruct'

const User = object({
  id: number(),
  name: string(),
  email: string(),
  password: string(),
})

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


Применение pick

import { pick } from 'superstruct'

const PublicUser = pick(User, ['id', 'name'])

Теперь PublicUser валидирует только:

  • id
  • name

Остальные поля полностью исключены из проверки.


Поведение валидации

При использовании pick:

  • отсутствующие поля не считаются ошибкой, если они не включены в новую схему
  • лишние поля не проходят проверку, поскольку они просто не существуют в целевой структуре
  • логика оригинальной структуры сохраняется только для выбранного подмножества

Практический смысл

pick используется для создания ограниченных представлений данных:

  • публичные DTO (Data Transfer Object)
  • ответы API без чувствительных полей
  • упрощённые модели для UI

Omit

Назначение

omit создаёт новую структуру, исключая указанные поля из исходной схемы. Остальные поля сохраняются без изменений.

Общая форма

const NewStruct = omit(OriginalStruct, ['fieldA', 'fieldB'])

Пример на той же структуре

import { omit } from 'superstruct'

const SafeUser = omit(User, ['password'])

В результате структура SafeUser содержит:

  • id
  • name
  • email

Поле password полностью исключено из схемы.


Логика работы omit

В отличие от ручного пересоздания схемы:

const SafeUser = object({
  id: number(),
  name: string(),
  email: string(),
})

omit позволяет:

  • сохранить исходную структуру как источник истины
  • уменьшить дублирование кода
  • автоматически синхронизировать изменения схемы

Поведение при изменениях исходной структуры

Если базовая структура изменяется:

const User = object({
  id: number(),
  name: string(),
  email: string(),
  password: string(),
  role: string(),
})

то omit(User, ['password']) автоматически включает новое поле role без необходимости модификации производной схемы.


Сравнение pick и omit

Операция Поведение Когда используется
pick включает только указанные поля публичные ответы, ограниченные модели
omit исключает указанные поля фильтрация чувствительных данных

Комбинирование pick и omit

В сложных схемах часто требуется последовательное применение трансформаций.

const Base = object({
  id: number(),
  name: string(),
  email: string(),
  password: string(),
  role: string(),
})

Пример цепочки

const Intermediate = omit(Base, ['password'])

const Public = pick(Intermediate, ['id', 'name'])

Результат:

  • остаются только id и name
  • исключение password гарантировано на раннем этапе

Взаимодействие с вложенными структурами

pick и omit работают на уровне ключей объекта и не выполняют глубокую трансформацию вложенных объектов автоматически.

const Profile = object({
  user: User,
  settings: object({
    theme: string(),
    notifications: boolean(),
  }),
})

Поведение

const PublicProfile = omit(Profile, ['user'])

Исключается только верхнеуровневый ключ user, вложенные структуры сохраняются без изменений.


Важно о глубокой фильтрации

Глубокая модификация требует явного определения:

const PublicUser = pick(User, ['id', 'name'])

const PublicProfile = object({
  user: PublicUser,
  settings: Profile.schema.settings,
})

Типичные сценарии использования

API слой

const CreateUser = pick(User, ['name', 'email', 'password'])
const UserResponse = omit(User, ['password'])

Админские интерфейсы

const AdminUserView = User

или с ограничениями:

const AdminUserView = omit(User, [])

Формы ввода

const LoginForm = pick(User, ['email', 'password'])

Ошибки при использовании

1. Дублирование логики вместо omit

Ручное создание новой структуры приводит к рассинхронизации:

// менее надёжно
const SafeUser = object({
  id: number(),
  name: string(),
})

2. Попытка глубокого pick без вложенных структур

pick(Profile, ['user.id']) // некорректный подход

Подобная запись не интерпретируется как путь.


3. Потеря актуальности схемы

При изменении базовой структуры производные схемы, созданные без pick/omit, требуют ручного обновления.


Архитектурная роль pick и omit

В контексте построения схем на базе Superstruct эти операции выполняют роль:

  • слоя абстракции над базовыми моделями
  • инструмента декомпозиции доменных объектов
  • механизма безопасного переиспользования структур

Они уменьшают связность между слоями приложения и повышают устойчивость к изменениям модели данных.


Использование в больших кодовых базах

В масштабных системах типичная структура выглядит следующим образом:

  • базовые доменные структуры (User, Order, Product)
  • производные схемы для API
  • производные схемы для форм
  • производные схемы для внутренних сервисов

pick и omit становятся инструментами управления вариациями одной и той же сущности без её дублирования.


Ограничения механизма

  • отсутствует глубокая модификация вложенных объектов
  • нет поддержки динамических путей
  • работает только с ключами верхнего уровня структуры
  • не заменяет полноценные трансформеры схем

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