zod icon indicating copy to clipboard operation
zod copied to clipboard

Property 'x' is optional in z.ZodType but required in TypeScript type?

Open lancejpollard opened this issue 1 year ago • 1 comments

I have this CodeSandbox, which has the following code. Notice that both in the TypeScript type, and the zod type, all properties are required. I am getting an error (shown below the code) which says that zod type is optional, which it shouldn't be.

import * as z from "zod";

export const FFMPEG_AUDIO_CODEC = ["foo", "bar"] as const;

export type FfmpegAudioCodec = (typeof FFMPEG_AUDIO_CODEC)[number];

export const FFMPEG_SUBTITLE_CODEC = ["foo", "bar"] as const;

export type FfmpegSubtitleCodec = (typeof FFMPEG_SUBTITLE_CODEC)[number];

export const FFMPEG_VIDEO_CODEC = ["foo", "bar"] as const;

export type FfmpegVideoCodec = (typeof FFMPEG_VIDEO_CODEC)[number];

export type ConvertVideoWithFfmpeg = {
  input: ArrayBuffer;
  output: ArrayBuffer;
  audioBitRate: number;
  audioChannels: number;
  audioCodec: FfmpegAudioCodec;
  audioSamplingFrequency: number;
  duration: number;
  endTime: number | string;
  frameRate: number;
  rotation: number;
  scaleHeight: number;
  scaleWidth: number;
  startTime: number;
  strict: boolean;
  subtitleCodec: FfmpegSubtitleCodec;
  videoBitRate: number;
  videoCodec: FfmpegVideoCodec;
};

export const FfmpegAudioCodecModel: z.ZodType<FfmpegAudioCodec> =
  z.enum(FFMPEG_AUDIO_CODEC);

export const FfmpegSubtitleCodecModel: z.ZodType<FfmpegSubtitleCodec> = z.enum(
  FFMPEG_SUBTITLE_CODEC,
);

export const FfmpegVideoCodecModel: z.ZodType<FfmpegVideoCodec> =
  z.enum(FFMPEG_VIDEO_CODEC);

export const ConvertVideoWithFfmpegModel: z.ZodType<ConvertVideoWithFfmpeg> =
  z.object({
    input: z.instanceof(ArrayBuffer),
    output: z.instanceof(ArrayBuffer),
    audioBitRate: z.number().int(),
    audioChannels: z.number().int(),
    audioCodec: z.lazy(() => FfmpegAudioCodecModel),
    audioSamplingFrequency: z.number(),
    duration: z.number().int(),
    endTime: z.union([z.number(), z.string()]),
    frameRate: z.number().int(),
    rotation: z.number(),
    scaleHeight: z.number().int(),
    scaleWidth: z.number().int(),
    startTime: z.number().int(),
    strict: z.boolean(),
    subtitleCodec: z.lazy(() => FfmpegSubtitleCodecModel),
    videoBitRate: z.number().int(),
    videoCodec: z.lazy(() => FfmpegVideoCodecModel),
  });

The last export const ConvertVideoWithFfmpegModel is throwing a TypeScript error:

Type 'ZodObject<{ input: ZodType<ArrayBuffer, ZodTypeDef, ArrayBuffer>; output: ZodType<ArrayBuffer, ZodTypeDef, ArrayBuffer>; ... 14 more ...; videoCodec: ZodLazy<...>; }, "strip", ZodTypeAny, { ...; }, { ...; }>' is not assignable to type 'ZodType<ConvertVideoWithFfmpeg, ZodTypeDef, ConvertVideoWithFfmpeg>'.
  Types of property '_type' are incompatible.
    Type '{ input?: ArrayBuffer; output?: ArrayBuffer; audioBitRate?: number; audioChannels?: number; audioCodec?: "foo" | "bar"; audioSamplingFrequency?: number; duration?: number; ... 9 more ...; videoCodec?: "foo" | "bar"; }' is not assignable to type 'ConvertVideoWithFfmpeg'.
      Property 'input' is optional in type '{ input?: ArrayBuffer; output?: ArrayBuffer; audioBitRate?: number; audioChannels?: number; audioCodec?: "foo" | "bar"; audioSamplingFrequency?: number; duration?: number; ... 9 more ...; videoCodec?: "foo" | "bar"; }' but required in type 'ConvertVideoWithFfmpeg'.typescript(2322)

I follow this pattern everywhere in my large codebase of doing:

export const MyModel: z.ZodType<MyType> = z.object(...)

Because I use z.lazy in many many places, so I ned to use z.ZodType. But for some reason it is saying the type is not required in the zode type, even though it is required in the TypeScript type. How do I fix this? Is this a bug, or what am I doing wrong?

Thanks for the help! Please let me know how to make the properties all required, _while still specifying the z.ZodType<T>.

Note, I get a different but similar error locally (it errors on a different property):

Screenshot 2024-03-01 at 7 25 41 PM

lancejpollard avatar Mar 02 '24 03:03 lancejpollard

I have this CodeSandbox as well, which demonstrates similar "optional" oriented errors when using .extend (and having to hack around the types):

import * as z from "zod";

export type FileContent = {
  content: ArrayBuffer | string;
};

export type FormatCodeWithClangFormatBrowserOutput = ClangStyleAll & {
  file: FileContent;
};

export type ClangStyleAll = {
  basedOnStyle?: string;
  alignAfterOpenBracket?: "align" | "dontAlign" | "alwaysBreak" | "blockIndent";
};

export const ClangStyleAllModel: z.ZodType<ClangStyleAll> = z.object({
  basedOnStyle: z.optional(z.string()),
  alignAfterOpenBracket: z.optional(
    z.enum(["align", "dontAlign", "alwaysBreak", "blockIndent"]),
  ),
});

export const FormatCodeWithClangFormatBrowserOutputModel: z.ZodType<FormatCodeWithClangFormatBrowserOutput> =
  (ClangStyleAllModel as z.ZodObject<z.ZodRawShape>).extend({
    file: z.lazy(() => FileContentModel),
  });

export const FileContentModel: z.ZodType<FileContent> = z.object({
  content: z.union([z.instanceof(ArrayBuffer), z.string()]),
});

How do I use .extend in this scenario, and how do I solve the optional errors, like this one?

Type 'ZodObject<{ [x: string]: ZodTypeAny; [x: number]: ZodTypeAny; file: ZodLazy<ZodType<FileContent, ZodTypeDef, FileContent>>; }, UnknownKeysParam, ZodTypeAny, { ...; }, { ...; }>' is not assignable to type 'ZodType<FormatCodeWithClangFormatBrowserOutput, ZodTypeDef, FormatCodeWithClangFormatBrowserOutput>'.
  Types of property '_type' are incompatible.
    Type '{ [x: string]: any; [x: number]: any; file?: FileContent; }' is not assignable to type 'FormatCodeWithClangFormatBrowserOutput'.
      Type '{ [x: string]: any; [x: number]: any; file?: FileContent; }' is not assignable to type '{ file: FileContent; }'.
        Property 'file' is optional in type '{ [x: string]: any; [x: number]: any; file?: FileContent; }' but required in type '{ file: FileContent; }'.typescript(2322)

Thank you.

lancejpollard avatar Mar 02 '24 16:03 lancejpollard

It looks like you don't have strict mode active in your tsconfig.json, that would account for why all the properties of your object schemas are optional.

colinhacks avatar Mar 13 '24 02:03 colinhacks

I have strict mode true and am running into this issue.

coler-j avatar Apr 10 '24 15:04 coler-j