Как извлечь один тип из типа союза Zod?
Я использую Zod , и у меня есть массив, содержащий различные объекты, использующие union. После разбора я хочу перебрать каждый элемент и извлечь его «настоящий» тип/отрезать другие типы.
При проверке определенных свойств объекта отлично работает следующий код:
const objectWithNumber = zod.object({ num: zod.number() });
const objectWithBoolean = zod.object({ isTruthy: zod.boolean() });
const myArray = zod.array(zod.union([objectWithNumber, objectWithBoolean]));
const parsedArray = myArray.parse([{ isTruthy: true }, { num: 3 }]);
parsedArray.forEach((item) => {
if ("num" in item) {
console.info('objectWithNumber:', item);
// TS knows about it => syntax support for objectWithNumber
} else if ("isTruthy" in item) {
console.info('objectWithBoolean:', item);
// TS knows about it => syntax support for objectWithBoolean
} else {
console.error('unknown');
}
});
Альтернативой может быть использование для этого дискриминационных союзов .
const objectWithNumber = zod.object({ type: zod.literal("objectWithNumber"), num: zod.number() });
const objectWithBoolean = zod.object({ type: zod.literal("objectWithBoolean"), isTruthy: zod.boolean() });
const myArray = zod.array(zod.discriminatedUnion("type", [ objectWithNumber, objectWithBoolean ]));
const parsedArray = myArray.parse([{ type: "objectWithBoolean", isTruthy: true }, { type: "objectWithNumber", num: 3 }]);
parsedArray.forEach(item => {
if (item.type === "objectWithNumber") {
console.info('objectWithNumber:', item);
// TS knows about it => syntax support for objectWithNumber
} else if (item.type === "objectWithBoolean") {
console.info('objectWithBoolean:', item);
// TS knows about it => syntax support for objectWithBoolean
} else {
console.error('unknown');
}
});
но я думаю, что неправильно понял эту концепцию, потому что нужно написать больше кода (я всегда могу добавить общее свойство и проверить его). Любая помощь в этом очень ценится :)
Существуют ли лучшие способы определения конкретной схемы?
1 ответ
Если я вас правильно понял, ваш вопрос сводится к тому, «Почему следует использовать размеченные союзы вместо общих полей в сочетании с необязательными полями». (
zod.js
просто переносит эту концепцию в среду выполнения, обеспечивая функциональность проверки). В вашем примере на самом деле нет причин использовать дискриминатор (
type
свойство), потому что каждый объект содержит только одно необязательное свойство, которое является взаимоисключающим между типами и может легко использоваться для различения типов. Однако основная проблема с этим кодом заключается в том, что имена объектов (или формы/структуры) не сообщают о каких-либо намерениях , поэтому трудно увидеть преимущества разграниченных объединений.
Вы можете думать о размеченном объединении как о типе, который описывает семейство объектов и предоставляет унифицированный механизм (в виде свойства дискриминатора) для их идентификации. Этот подход менее уязвим, чем проверка существования некоторых выбранных вручную свойств (например, свойства экземпляра). Что если
num
по какой-либо причине становится необязательным? Тогда ваша проверка существования num сломается. Другим аргументом в пользу размеченного объединения является уменьшение количества необязательных свойств. Сравните два следующих примера:
// shared and optional fields instead of discriminated union
type Vehicle = {
name: string;
combustionEngine?: PetrolEngine | DieselEngine;
tankCapacity?: number;
electricEngine?: ElectricEngine;
batteryCapacity?: number;
}
type Vehicle =
| GasolineCar
| ElectricCar
type = GasolineCar {
kind: "gasolineCar";
name: string;
engine: PetrolEngine | DieselEngine;
tankCapacity: number;
}
type ElectricCar = {
kind: "electricCar"
name: string;
engine: ElecticEngine;
batteryCapacity: number;
}
Пример с размеченным объединением дает гораздо более описательный код. Вам не нужно добавлять несколько проверок для необязательных полей — вместо этого просто определите (как можно раньше) тип по дискриминатору и передайте объект функции/методу, принимающему более узкий тип (
GasolineCar
или же
ElectricCar
вместо
Vehicle
).