typescript - Hur man skapar ett flernivåmenyschema i Zod
Jag försöker skapa ett flernivå-schema för Zod som kan härledas från dessa data:
{
"data": [
{
"title": "Devices",
"items": [
{
"items": [
{
"title": "something",
"url": "/products/devices/something"
},
{
"title": "something3",
"url": "/products/devices/something3"
},
{
"title": "something2",
"url": "/products/devices/something2"
}
],
"title": "Flower + Concentrates",
"url": "/devices/flower-and-concentrate"
},
{
"items": [
{
"title": "something",
"url": "/products/devices/something"
},
{
"title": "something3",
"url": "/products/devices/something3"
},
{
"title": "something2",
"url": "/products/devices/something2"
}
],
"title": "something",
"url": "/devices/something"
}
]
},
{
"title": "CBD",
"items": [
{
"title": "something",
"url": "/products/devices/something"
},
{
"title": "something3",
"url": "/products/devices/something3"
},
{
"title": "something2",
"url": "/products/devices/something2"
}
]
}
...
Hittills har jag:
export const menuItemSchema = z.object({
url: z.string(),
title: z.string(),
});
export const menuSchema = z.object({
url: z.string().optional(),
title: z.string(),
items: z.array(menuItemSchema),
});
Jag kan dock inte lista ut hur man gör menuSchema rekursiv för att hantera flernivåmenyer. Jag har läst dokumentationen och ser inget om detta. För sammanhanget använder jag ett CMS för att hämta denna data och Zod för att säkerställa att alla menyer följer detta schema. Så det borde också kunna fungera på envåningsmenyer.
REDIGERA :
Jag har hittat en bit dokumentation för rekursion, men det verkar som att jag kan ha gjort ett misstag någonstans eftersom du kan skicka in ytterligare nycklar där jag bara vill ha title, items, och url:
const baseMenu = z.object({
title: z.string().optional(),
url: z.string().optional(),
});
type Menu = z.infer<typeof baseMenu> & {
items?: Menu[];
};
const MenuSchema: z.ZodType<Menu> = baseMenu.extend({
items: z.lazy(() => MenuSchema.array().optional()),
});
console.log(MenuSchema.safeParse({
data: [
{
title: 'Devices',
items: [
{
items: [
{
title: 'something',
url: '/products/devices/something',
},
{
title: 'something3',
url: '/products/devices/something3',
},
{
title: 'something2',
url: '/products/devices/something2',
},
],
title: 'Flower + Concentrates',
url: '/devices/flower-and-concentrate',
},
{
items: [
{
title: 'something',
url: '/products/devices/something',
},
{
title: 'something3',
url: '/products/devices/something3',
},
{
title: 'something2',
url: '/products/devices/something2',
},
],
title: 'something',
url: '/devices/something',
},
],
},
{
title: 'CBD',
items: [
{
title: 'something',
url: '/products/devices/something',
},
{
title: 'something3',
url: '/products/devices/something3',
},
{
title: 'something2',
url: '/products/devices/something2',
},
],
},
],
}))
Svar #1
När man tittar på datan har jag identifierat två olika typer. Det finns en toppnivåmenytyp som inte har någon URL och sedan finns det menyobjekt som har en url, title och eventuellt en rekursiv lista med menyobjekt. Jag tror att det som gör att du fastnar är att toppnivåmenytypen känns väldigt lik menyobjekten, men jag tycker att det är bättre att betrakta dem som två olika typer. Med detta i åtanke kan du formulera dina scheman på följande sätt:
import { z } from "zod";
// This is a bit upside down, but we specify the recursive inner type
// first because we're going to use it with the top level menu schema later.
interface MenuItem {
title: string;
items?: MenuItem[];
url: string;
}
// Recursive types rely on the type already existing, it can't infer
// what the type should be which is why there is an explicit interface
// definition.
const MenuItemSchema: z.ZodType<MenuItem> = z.lazy(() => z.object({
title: z.string(),
// Recursive use of the schema
items: MenuItemSchema.array().optional(),
url: z.string(),
}));
// A fully separate type for the outermost menu
const TopLevelMenuSchema = z.object({
title: z.string(),
items: MenuItemSchema.array(),
});
type TopLevelMenu = z.infer<typeof TopLevelMenuSchema>;
Då kan du analysera dina indata med något som
z.object({ data: TopLevelMenuSchema.array() }).safeParse(data);
