Strip и keep

Валидация в Joi не ограничивается проверкой типов и ограничений. Существенная часть работы библиотеки — управление тем, какие поля остаются в результате после валидации, а какие должны быть удалены. Это особенно важно при работе с API, где входящие данные часто содержат лишние или потенциально опасные поля.

Удаление лишних полей через strip

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

Базовое использование strip

const Joi = require('joi');

const schema = Joi.object({
  username: Joi.string().min(3).required(),
  password: Joi.string().min(6).required(),
  role: Joi.string().valid('user', 'admin').strip()
});

const result = schema.validate({
  username: 'alex',
  password: '123456',
  role: 'admin'
});

console.log(result.value);

Результат:

{
  username: 'alex',
  password: '123456'
}

Поле role прошло валидацию, но было удалено из итогового объекта.

Принцип работы strip

  • strip() не влияет на процесс проверки значения
  • поле валидируется как обычно
  • после успешной валидации оно удаляется из value
  • полезно для скрытия служебных или чувствительных данных

Пример с несколькими полями

const schema = Joi.object({
  id: Joi.number().integer().required(),
  email: Joi.string().email().required(),
  internalFlag: Joi.boolean().strip(),
  debugInfo: Joi.string().strip()
});

В результате остаются только id и email, остальные поля исчезают независимо от их валидности.

Глобальное удаление неизвестных полей

Помимо точечного strip, Joi поддерживает удаление всех неизвестных ключей через опцию stripUnknown.

const schema = Joi.object({
  name: Joi.string().required()
}).options({ stripUnknown: true });

const result = schema.validate({
  name: 'John',
  age: 30,
  city: 'Astana'
});

Результат:

{
  name: 'John'
}

Все поля, не описанные в схеме, удаляются автоматически.

Взаимодействие strip и stripUnknown

При одновременном использовании действуют оба механизма:

const schema = Joi.object({
  name: Joi.string().required(),
  token: Joi.string().strip()
}).options({ stripUnknown: true });

Вход:

{
  name: 'Anna',
  token: 'secret',
  role: 'admin'
}

Результат:

{
  name: 'Anna'
}
  • role удалён как неизвестный
  • token удалён по явному правилу .strip()

Сохранение полей через keep

Механизм keep используется как противоположность strip. Он гарантирует, что поле не будет удалено даже в случаях, когда включено глобальное удаление неизвестных значений.

Основное применение keep

const schema = Joi.object({
  name: Joi.string().required(),
  meta: Joi.object().unknown(true).keep()
}).options({ stripUnknown: true });

const result = schema.validate({
  name: 'Ivan',
  meta: {
    ip: '127.0.0.1',
    session: 'abc123'
  }
});

Результат:

{
  name: 'Ivan',
  meta: {
    ip: '127.0.0.1',
    session: 'abc123'
  }
}

Несмотря на stripUnknown, объект meta сохранён полностью.

Сценарии использования keep

keep применяется в случаях, когда:

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

Приоритет strip и keep

При конфликте правил действует следующая логика:

  • strip() имеет приоритет над глобальным сохранением
  • keep() может переопределить stripUnknown, но не отменяет явный .strip() на поле

Пример конфликта

const schema = Joi.object({
  data: Joi.string().strip().keep()
}).options({ stripUnknown: true });

Поле data будет удалено, потому что:

  • .strip() имеет более высокий приоритет, чем .keep()

Использование strip в вложенных объектах

const schema = Joi.object({
  user: Joi.object({
    id: Joi.number().required(),
    password: Joi.string().strip()
  })
});

Результат:

{
  user: {
    id: 1
  }
}

Удаление применяется на уровне вложенной структуры без дополнительной настройки.

Практика безопасной обработки данных

Комбинация strip, keep и stripUnknown часто используется для построения безопасного слоя валидации входящих данных:

const schema = Joi.object({
  username: Joi.string().required(),
  password: Joi.string().required(),
  role: Joi.string().valid('user', 'admin').strip(),
  meta: Joi.object().unknown(true).keep()
}).options({ stripUnknown: true });

Такая схема обеспечивает:

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

Особенности поведения при кастинге

Важно учитывать, что Joi сначала приводит данные к валидной форме (coercion), и только затем применяет strip.

const schema = Joi.object({
  age: Joi.number().strip()
});

const result = schema.validate({ age: '25' });

Даже после преобразования '25' → 25, поле будет удалено из результата.

Влияние на производственные сценарии

Механизмы strip и keep активно используются в:

  • API-валидации запросов
  • фильтрации пользовательских данных
  • обработке JWT payload
  • защите от mass assignment атак
  • построении DTO-слоёв

Их грамотное применение позволяет контролировать форму данных без дополнительной ручной фильтрации.