В JavaScript структуры Set и Map
используются для хранения уникальных значений и коллекций пар
ключ–значение. Библиотека Zod предоставляет специальные схемы для
строгой валидации этих структур:
z.set()z.map()Обе схемы поддерживают:
SetSetДля описания множества используется z.set().
import { z } from "zod";
const tagsSchema = z.set(z.string());
tagsSchema.parse(new Set(["js", "ts"]));
Схема означает:
Set;tagsSchema.parse(new Set(["js", 123]));
Ошибка:
Expected string, received number
Zod проверяет каждый элемент множества отдельно.
Setconst rolesSchema = z.set(z.string());
type Roles = z.infer<typeof rolesSchema>;
Результат:
type Roles = Set<string>;
const schema = z.set(z.string()).min(2);
Допустимые значения:
new Set(["admin", "user"])
Недопустимые:
new Set(["admin"])
const schema = z.set(z.string()).max(5);
const schema = z.set(z.string()).size(3);
Только множества из трёх элементов будут валидны.
const schema = z
.set(z.string())
.min(2, "Минимум два элемента");
Setconst userSchema = z.object({
id: z.number(),
name: z.string(),
});
const usersSchema = z.set(userSchema);
Использование:
usersSchema.parse(
new Set([
{ id: 1, name: "Alex" },
{ id: 2, name: "John" },
])
);
Setconst roleSchema = z.enum(["admin", "user", "moderator"]);
const rolesSchema = z.set(roleSchema);
Допустимо:
new Set(["admin", "user"])
Недопустимо:
new Set(["superadmin"])
refineconst schema = z
.set(z.string())
.refine(
(set) => set.has("admin"),
{
message: "Множество должно содержать admin",
}
);
Setconst schema = z
.set(z.string())
.transform((set) => [...set]);
Результат:
["a", "b", "c"]
const schema = z
.set(z.string())
.transform((set) => [...set].sort());
Setconst schema = z.preprocess(
(value) => {
if (Array.isArray(value)) {
return new Set(value);
}
return value;
},
z.set(z.string())
);
Теперь массив автоматически преобразуется в Set.
schema.parse(["a", "b", "c"]);
safeParse для
Setconst result = z
.set(z.number())
.safeParse(new Set([1, 2, "3"]));
Результат:
{
success: false,
error: ZodError
}
SetparseAsyncconst schema = z
.set(z.string())
.refine(async (set) => {
return set.size > 0;
});
await schema.parseAsync(new Set(["a"]));
MapДля Map используется z.map().
const schema = z.map(
z.string(),
z.number()
);
Первый аргумент:
Второй аргумент:
schema.parse(
new Map([
["apples", 10],
["oranges", 20],
])
);
schema.parse(
new Map([
[123, 10],
])
);
Ошибка:
Expected string, received number
schema.parse(
new Map([
["apples", "10"],
])
);
Mapconst schema = z.map(
z.string(),
z.boolean()
);
type Flags = z.infer<typeof schema>;
Результат:
type Flags = Map<string, boolean>;
Mapconst productSchema = z.object({
id: z.number(),
title: z.string(),
});
const schema = z.map(
z.string(),
productSchema
);
const keySchema = z.enum([
"admin",
"user",
]);
const schema = z.map(
keySchema,
z.boolean()
);
Mapconst schema = z
.map(z.string(), z.number())
.min(1);
const schema = z
.map(z.string(), z.number())
.max(10);
const schema = z
.map(z.string(), z.number())
.size(2);
refineconst schema = z
.map(z.string(), z.number())
.refine(
(map) => {
for (const value of map.values()) {
if (value < 0) {
return false;
}
}
return true;
},
{
message: "Отрицательные значения запрещены",
}
);
Mapconst schema = z
.map(z.string(), z.number())
.transform((map) =>
Object.fromEntries(map)
);
Результат:
{
apples: 10,
oranges: 20
}
Mappreprocessconst schema = z.preprocess(
(value) => {
if (
typeof value === "object" &&
value !== null &&
!Array.isArray(value)
) {
return new Map(
Object.entries(value)
);
}
return value;
},
z.map(z.string(), z.number())
);
Использование:
schema.parse({
apples: 10,
oranges: 20,
});
Map со значениями
Setconst schema = z.map(
z.string(),
z.set(z.string())
);
Пример:
new Map([
["admins", new Set(["alex"])],
["users", new Set(["john"])],
]);
Set объектов с
Mapconst schema = z.set(
z.object({
name: z.string(),
permissions: z.map(
z.string(),
z.boolean()
),
})
);
Setconst schema = z
.set(z.string())
.nullable();
Допустимые значения:
null
new Set(["a"])
Mapconst schema = z
.map(z.string(), z.number())
.optional();
defaultconst schema = z
.set(z.string())
.default(new Set());
Mapconst schema = z
.map(z.string(), z.number())
.default(new Map());
unionconst schema = z.union([
z.set(z.string()),
z.array(z.string()),
]);
instanceofХотя z.set() и z.map() предпочтительнее,
иногда используется instanceof.
Setconst schema = z.instanceof(Set);
Mapconst schema = z.instanceof(Map);
Однако такой подход:
Set от
массива в Zodz.array()z.array(z.string())
Особенности:
z.set()z.set(z.string())
Особенности:
Set.Map от объектаz.object()z.object({
name: z.string(),
})
Статическая структура.
z.map()z.map(z.string(), z.number())
Динамический набор ключей.
const permissionsSchema = z.map(
z.string(),
z.set(
z.enum([
"read",
"write",
"delete",
])
)
);
Пример данных:
const permissions = new Map([
[
"admin",
new Set([
"read",
"write",
"delete",
]),
],
[
"user",
new Set(["read"]),
],
]);
Валидация:
permissionsSchema.parse(
permissions
);
const cacheSchema = z.map(
z.string(),
z.object({
createdAt: z.date(),
value: z.unknown(),
})
);
const tagsSchema = z
.set(z.string())
.transform((set) =>
[...set].map((tag) =>
tag.toLowerCase()
)
);
try {
z.set(z.number()).parse(
new Set([1, "2"])
);
} catch (err) {
console.log(err.errors);
}
Результат:
[
{
code: "invalid_type",
expected: "number",
received: "string",
path: [1],
message: "Expected number, received string"
}
]
SetПри валидации:
На больших множествах сложные refine могут существенно
влиять на производительность.
MapДля Map отдельно валидируются:
Чем сложнее вложенные схемы, тем выше стоимость проверки.
SetПодходит для:
MapПодходит для:
Set и
MapJSON не поддерживает:
Set;Map.Перед сериализацией необходимо преобразование.
Setconst serialized = JSON.stringify([
...mySet
]);
Mapconst serialized = JSON.stringify(
Object.fromEntries(myMap)
);
Setconst set = new Set(
JSON.parse(json)
);
Mapconst map = new Map(
Object.entries(
JSON.parse(json)
)
);