Вопрос или проблема
Вот код, отвечающий за валидацию схемы формы в нашем фронтенд-приложении.
У нас возникают проблемы с библиотекой Zod при доступе к свойству конкретной ветви discriminatedUnion
из колбека валидации refine()
. Свойство endDate
отсутствует в автозаполнении, свойство подчеркивается красным языковым сервером Typescript с сообщением “Свойство ‘endDate’ не существует для типа …”.
Как должны быть доступны свойства конкретных ветвей в этом контексте?
export const formSchema = z.object({
// Несколько полей здесь ...
contract: z.object({
startDate: z.string().describe('текст'),
contractType: z.enum(['III', 'DDD']).describe('выбор'),
// Несколько других полей здесь ...
specificationContrat: z.discriminatedUnion('contractType', [
z.object({
contractType: z.literal('III'),
...someSchema.shape,
}),
z.object({
contractType: z.literal('DDD'),
endDate: z.string().describe('текст'),
...someSchema.shape,
})
]),
})
.refine(
(contract) => {
return contract.contractType === 'III' ||
(contract.contractType === 'DDD' && new Date(contract.specificationContrat.endDate) > new Date(contract.startDate));
// "Свойство 'endDate' не существует для типа …"______________________________^
},
'"endDate" должно быть ранее "startDate"'
)
});
Ответ или решение
Для решения проблемы с валидацией с использованием библиотеки Zod и доступом к специфическим свойствам ветвей типа discriminatedUnion
, необходимо учитывать, что TypeScript не может гарантировать, что определенное свойство доступно для всех ветвей объединения. В вашем случае, свойство endDate
отсутствует в ветви, соответствующей типу III
, что и приводит к ошибке компиляции.
Чтобы получить доступ к свойству endDate
в функции валидации refine
, следует использовать метод приведения типов. Это можно сделать с помощью конструкции if
для проверки типа контракта перед обращением к свойству. Вот как можно переписать вашу функцию валидации для обеспечения правильного доступа к endDate
:
import { z } from 'zod';
const formSchema = z.object({
// Другие поля ...
contract: z.object({
startDate: z.string().describe('text'),
contractType: z.enum(['III', 'DDD']).describe('select'),
// Другие поля ...
specificationContrat: z.discriminatedUnion('contractType', [
z.object({
contractType: z.literal('III'),
// Поля для контракта типа III...
}),
z.object({
contractType: z.literal('DDD'),
endDate: z.string().describe('text'),
// Поля для контракта типа DDD...
})
]),
})
.refine((contract) => {
if (contract.contractType === 'III') {
return true; // Валидация для III типа
} else if (contract.contractType === 'DDD') {
const endDate = contract.specificationContrat.endDate;
return new Date(endDate) > new Date(contract.startDate);
}
return false; // На всякий случай, если контрактType не соответствует ни одному из вариантов
}, '"endDate" должен быть позднее "startDate"')
});
Разбор решения:
-
Проверка типа: Используя конструкцию
if
, мы проверяем тип контракта до того, как мы обращаемся кendDate
. Это позволяет TypeScript гарантировать, что мы работаем с ветвью, которая действительно содержит это свойство. -
Чистота кода: Функция валидации остаётся понятной и модульной, так как мы явно обрабатываем оба случая. Если тип контракта не соответствует ни одному из ожидаемых значений, возвращаем
false
для предотвращения неявного поведения. -
Поддержка TypeScript: Данное решение устраняет проблемы с подчеркиванием в IDE, так как TypeScript теперь может извлечь информацию о типах на основе проверок, во избежание доступа к несуществующим свойствам.
Понимание работы discriminatedUnion
и правильная работа с конкретными ветвями являются ключевыми факторами при использовании библиотеки Zod для валидации данных. Следуя предложенному подходу, вы сможете избежать типовых ошибок и обеспечить корректную валидацию ваших данных.