typescript 如何在Zod中创建多级菜单模式

fykwrbwg  于 2023-02-10  发布在  TypeScript
关注(0)|答案(1)|浏览(129)

我正在尝试为Zod创建一个可以从以下数据派生的多级模式:

{
    "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"
                }
            ]
        }
  ...

到目前为止我有:

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),
});

然而,我不知道如何使menuSchema递归以考虑多级菜单。我已经阅读了文档,但没有看到任何关于这方面的内容。对于上下文,我使用CMS来获取此数据,并使用Zod来确保所有菜单都遵守此模式。因此,它也应该能够在一级菜单上工作。
编辑:
我找到了一个递归的文档,但是看起来我可能在某个地方搞砸了,因为你可以在我只需要title, items, and 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',
        },
      ],
    },
  ],
}))
n6lpvg4x

n6lpvg4x1#

通过查看数据,我确定了两种不同的类型。一种是没有URL的顶级菜单项类型,另一种是具有urltitle以及可选的菜单项递归列表的菜单项。我认为您陷入困境的地方在于顶级菜单项类型与菜单项非常相似。但我认为最好将它们视为两种不同的类型。记住这一点,您可以将您的模式表述如下:

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>;

然后,您可以使用类似下面的语句来解析输入数据

z.object({ data: TopLevelMenuSchema.array() }).safeParse(data);

相关问题