Валидация числовых значений на кратность широко используется при
описании бизнес-правил: шаги, интервалы, сетки, ограничения на ввод,
дискретные величины. В Zod подобные условия реализуются через комбинацию
базовой числовой схемы и пользовательских проверок (refine,
superRefine), поскольку встроенного оператора «кратно»
нет.
Базовая схема числового значения задаётся через
z.number(), после чего накладывается дополнительное условие
кратности:
import { z } from "zod";
const multipleOfTen = z.number().refine((value) => value % 10 === 0, {
message: "Число должно быть кратно 10",
});
Операция остатка от деления (%) используется как
основной механизм проверки. Если результат равен нулю, число считается
кратным указанному делителю.
Кратность часто требуется не к фиксированному значению, а к переменной. В таких случаях создаётся фабрика схем:
import { z } from "zod";
const multipleOf = (divisor) =>
z.number().refine((value) => value % divisor === 0, {
message: `Число должно быть кратно ${divisor}`,
});
const multipleOfFive = multipleOf(5);
const multipleOfTwelve = multipleOf(12);
Такой подход позволяет переиспользовать логику без дублирования кода и формировать набор правил в зависимости от контекста.
При необходимости учитывать дополнительные условия применяется
superRefine, предоставляющий доступ к контексту ошибок:
const schema = z.number().superRefine((value, ctx) => {
if (value % 3 !== 0) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Число не кратно 3",
});
}
if (value < 0) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Число должно быть положительным",
});
}
});
superRefine позволяет агрегировать несколько проверок в
рамках одной схемы без разрыва цепочки валидации.
Остаток от деления применяется не только для кратности, но и для классификации значений. Например, разбиение на категории по модулю:
const categoryByModulo = z.number().refine((value) => {
const remainder = value % 4;
return remainder === 1 || remainder === 3;
}, {
message: "Остаток от деления должен быть 1 или 3",
});
Такой подход используется при распределении значений по циклическим структурам, например расписаниям или индексам кольцевых буферов.
Часто входные данные приходят в строковом виде, что требует предварительного преобразования:
const multipleOfSeven = z
.string()
.transform((val) => Number(val))
.refine((value) => value % 7 === 0, {
message: "Число должно быть кратно 7",
});
transform выполняет нормализацию данных до применения
логики проверки, что особенно важно при работе с формами и внешними
API.
Кратность может быть частью более крупной структуры данных:
const productSchema = z.object({
quantity: z.number().refine((value) => value % 2 === 0, {
message: "Количество должно быть кратным 2",
}),
price: z.number().positive(),
});
В таких схемах каждая бизнес-ограниченность выражается локально, сохраняя читаемость и предсказуемость структуры.
Иногда делитель зависит от другого поля:
const schema = z.object({
base: z.number(),
value: z.number(),
}).refine((data) => data.value % data.base === 0, {
message: "value должно быть кратно base",
});
Здесь проверка выходит за пределы одного поля и требует анализа всей структуры объекта.
Оператор остатка в JavaScript сохраняет знак делимого, что может влиять на проверку:
(-10) % 3 // -1
Поэтому при строгой математической логике часто нормализуют значение:
const isMultiple = (value, divisor) =>
Math.abs(value % divisor) === 0;
Такая нормализация делает поведение проверки предсказуемым независимо от знака числа.
Часто кратность комбинируется с ограничением диапазона:
const schema = z.number().refine((value) => {
return value >= 100 && value <= 1000 && value % 50 === 0;
}, {
message: "Число должно быть в диапазоне 100–1000 и кратно 50",
});
Подобная конструкция применяется для шаговых значений: проценты, уровни, сетки координат.
Остаток от деления используется для разбиения значений на группы:
const schema = z.number().refine((value) => {
const group = value % 5;
return group === 0;
}, {
message: "Значение должно попадать в допустимую группу",
});
Такой подход формирует дискретные множества значений без явного перечисления всех допустимых вариантов.
При использовании superRefine возможно более точное
указание источника ошибки:
const schema = z.number().superRefine((value, ctx) => {
if (value % 9 !== 0) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: [],
message: "Нарушена кратность 9",
});
}
});
path позволяет локализовать ошибку в сложных структурах,
где проверка не привязана к конкретному полю напрямую.
Модульная арифметика становится базовым инструментом для описания циклических ограничений: расписания, повторяющиеся интервалы, сегментация данных. В связке с Zod она реализуется через функциональные проверки, обеспечивая гибкость без необходимости расширения библиотеки специализированными типами.