colinhacks / zod

TypeScript-first schema validation with static type inference
https://zod.dev
MIT License
33.09k stars 1.15k forks source link

Recursive schema with lazy() doesn't seem to work #3628

Open dearlordylord opened 2 months ago

dearlordylord commented 2 months ago

reproduction https://github.com/dearlordylord/zod-recursive-problem

similar problems exist https://github.com/colinhacks/zod/issues/3560 , https://github.com/colinhacks/zod/issues/3331

but they seem to yield different errors and have different reasons

Context

I have a file system-like structure where a node is a discriminated union of a leaf (file) or a node with n leaves (directory)

(the type signature goes further in the code example and in the repo )

I tried another parser library https://github.com/effect-ts/effect/tree/main/packages/schema and can pull it off with both compile time and runtime

however, Zod doesn't seem to compile this correctly, but it does run and validate correctly runtime:

(the same code is in counter.tsx of https://github.com/dearlordylord/zod-recursive-problem )


import { z } from 'zod';

type FileSystem = (
  | {
  readonly type: 'directory';
  readonly children: readonly FileSystem[];
}
  | {
  readonly type: 'file';
}
  ) & {
  readonly name: FileSystemName;
};

const fileSystemNameSchema = z.string().min(1).max(255).brand('fileSystemName');
type FileSystemName = z.infer<typeof fileSystemNameSchema>;

const fileSystemBaseSchema = z.object({
  name: fileSystemNameSchema
});

const fileSystemDirectoryBaseSchema = fileSystemBaseSchema.extend({
  type: z.literal('directory')
});

type FileSystemDirectory = z.infer<typeof fileSystemDirectoryBaseSchema> & {
  readonly children: readonly FileSystem[];
}

const fileSystemDirectorySchema: z.ZodType<FileSystemDirectory> = fileSystemDirectoryBaseSchema.extend({
  children: z.lazy(() => z.array(fileSystemSchema))
});

const fileSystemFileSchema = fileSystemBaseSchema.extend({
  type: z.literal('file')
});

export const fileSystemSchema = z.discriminatedUnion('type', [
  fileSystemDirectorySchema,
  fileSystemFileSchema
]);

the error is

src/counter.ts:30:7 - error TS2322: Type 'ZodObject<extendShape<extendShape<{ name: ZodBranded<ZodString, "fileSystemName">; }, { type: ZodLiteral<"directory">; }>, { children: ZodLazy<ZodArray<ZodDiscriminatedUnion<"type", [...]>, "many">>; }>, "strip", ZodTypeAny, { ...; }, { ...; }>' is not assignable to type 'ZodType<FileSystemDirectory, ZodTypeDef, FileSystemDirectory>'.
  Types of property '_type' are incompatible.
    Type '{ type: "directory"; name: string & BRAND<"fileSystemName">; children: { [x: string]: any; type?: unknown; }[]; }' is not assignable to type 'FileSystemDirectory'.
      Type '{ type: "directory"; name: string & BRAND<"fileSystemName">; children: { [x: string]: any; type?: unknown; }[]; }' is not assignable to type '{ readonly children: readonly FileSystem[]; }'.
        Types of property 'children' are incompatible.
          Type '{ [x: string]: any; type?: unknown; }[]' is not assignable to type 'readonly FileSystem[]'.
            Type '{ [x: string]: any; type?: unknown; }' is not assignable to type 'FileSystem'.
              Type '{ [x: string]: any; type?: unknown; }' is not assignable to type '{ readonly type: "directory"; readonly children: readonly FileSystem[]; } & { readonly name: string & BRAND<"fileSystemName">; }'.
                Property 'children' is missing in type '{ [x: string]: any; type?: unknown; }' but required in type '{ readonly type: "directory"; readonly children: readonly FileSystem[]; }'.

zod ^3.23.8