Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions packages/bench/object-moltar-jitless.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import * as z4 from "zod/v4";
import * as z3 from "zod3";
import * as z4lib from "zod4/v4";
import { metabench } from "./metabench.js";

z4.config({
jitless: true,
});

const z3Schema = z3.strictObject({
number: z3.number(),
negNumber: z3.number(),
maxNumber: z3.number(),
string: z3.string(),
longString: z3.string(),
boolean: z3.boolean(),
deeplyNested: z3.strictObject({
foo: z3.string(),
num: z3.number(),
bool: z3.boolean(),
}),
});

const z4LibSchema = z4lib.strictObject({
number: z4lib.number(),
negNumber: z4lib.number(),
maxNumber: z4lib.number(),
string: z4lib.string(),
longString: z4lib.string(),
boolean: z4lib.boolean(),
deeplyNested: z4lib.strictObject({
foo: z4lib.string(),
num: z4lib.number(),
bool: z4lib.boolean(),
}),
});

const z4Schema = z4.strictObject({
number: z4.number(),
negNumber: z4.number(),
maxNumber: z4.number(),
string: z4.string(),
longString: z4.string(),
boolean: z4.boolean(),
deeplyNested: z4.strictObject({
foo: z4.string(),
num: z4.number(),
bool: z4.boolean(),
}),
});

const DATA = Array.from({ length: 1000 }, () =>
Object.freeze({
number: 1,
negNumber: -1,
maxNumber: Number.MAX_VALUE,
string: "string",
longString:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Vivendum intellegat et qui, ei denique consequuntur vix. Semper aeterno percipit ut his, sea ex utinam referrentur repudiandae. No epicuri hendrerit consetetur sit, sit dicta adipiscing ex, in facete detracto deterruisset duo. Quot populo ad qui. Sit fugit nostrum et. Ad per diam dicant interesset, lorem iusto sensibus ut sed. No dicam aperiam vis. Pri posse graeco definitiones cu, id eam populo quaestio adipiscing, usu quod malorum te. Ex nam agam veri, dicunt efficiantur ad qui, ad legere adversarium sit. Commune platonem mel id, brute adipiscing duo an. Vivendum intellegat et qui, ei denique consequuntur vix. Offendit eleifend moderatius ex vix, quem odio mazim et qui, purto expetendis cotidieque quo cu, veri persius vituperata ei nec. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.",
boolean: true,
deeplyNested: {
foo: "bar",
num: 1,
bool: false,
},
})
);

console.log(z3Schema.parse(DATA[0]));
console.log(z4Schema.parse(DATA[0]));
console.log(z4LibSchema.parse(DATA[0]));

const bench = metabench("z.object() safeParse", {
zod3() {
for (const _ of DATA) z3Schema.parse(_);
},
zod4lib() {
for (const _ of DATA) z4LibSchema.parse(_);
},
zod4() {
for (const _ of DATA) z4Schema.parse(_);
},
});

await bench.run();
28 changes: 0 additions & 28 deletions packages/bench/object-moltar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,34 +45,6 @@ const z4Schema = z4.strictObject({
}),
});

// const z4SchemaStrict = z4.strictObject({
// number: z4.number(),
// negNumber: z4.number(),
// maxNumber: z4.number(),
// string: z4.string(),
// longString: z4.string(),
// boolean: z4.boolean(),
// deeplyNested: z4.strictObject({
// foo: z4.string(),
// num: z4.number(),
// bool: z4.boolean(),
// }),
// });

// const z4SchemaLoose = z4.object({
// number: z4.number(),
// negNumber: z4.number(),
// maxNumber: z4.number(),
// string: z4.string(),
// longString: z4.string(),
// boolean: z4.boolean(),
// deeplyNested: z4.object({
// foo: z4.string(),
// num: z4.number(),
// bool: z4.boolean(),
// }),
// });

const DATA = Array.from({ length: 1000 }, () =>
Object.freeze({
number: 1,
Expand Down
27 changes: 27 additions & 0 deletions packages/zod/src/v4/classic/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ export interface ZodType<

// wrappers
optional(): ZodOptional<this>;
exactOptional(): ZodExactOptional<this>;
nonoptional(params?: string | core.$ZodNonOptionalParams): ZodNonOptional<this>;
nullable(): ZodNullable<this>;
nullish(): ZodOptional<ZodNullable<this>>;
Expand Down Expand Up @@ -214,6 +215,7 @@ export const ZodType: core.$constructor<ZodType> = /*@__PURE__*/ core.$construct

// wrappers
inst.optional = () => optional(inst);
inst.exactOptional = () => exactOptional(inst);
inst.nullable = () => nullable(inst);
inst.nullish = () => optional(nullable(inst));
inst.nonoptional = (params) => nonoptional(inst, params);
Expand Down Expand Up @@ -1845,6 +1847,31 @@ export function optional<T extends core.SomeType>(innerType: T): ZodOptional<T>
}) as any;
}

// ZodExactOptional
export interface ZodExactOptional<T extends core.SomeType = core.$ZodType>
extends _ZodType<core.$ZodExactOptionalInternals<T>>,
core.$ZodExactOptional<T> {
"~standard": ZodStandardSchemaWithJSON<this>;
unwrap(): T;
}
export const ZodExactOptional: core.$constructor<ZodExactOptional> = /*@__PURE__*/ core.$constructor(
"ZodExactOptional",
(inst, def) => {
core.$ZodExactOptional.init(inst, def);
ZodType.init(inst, def);
inst._zod.processJSONSchema = (ctx, json, params) => processors.optionalProcessor(inst, ctx, json, params);

inst.unwrap = () => inst._zod.def.innerType;
}
);

export function exactOptional<T extends core.SomeType>(innerType: T): ZodExactOptional<T> {
return new ZodExactOptional({
type: "optional",
innerType: innerType as any as core.$ZodType,
}) as any;
}

// ZodNullable
export interface ZodNullable<T extends core.SomeType = core.$ZodType>
extends _ZodType<core.$ZodNullableInternals<T>>,
Expand Down
87 changes: 87 additions & 0 deletions packages/zod/src/v4/classic/tests/optional.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,90 @@ test("optional prop with pipe", () => {
schema.parse({});
schema.parse({}, { jitless: true });
});

// exactOptional tests
test(".exactOptional()", () => {
const schema = z.string().exactOptional();
expect(schema.parse("asdf")).toEqual("asdf");
expect(schema.safeParse(undefined).success).toEqual(false);
expect(schema.safeParse(null).success).toEqual(false);

// Type should NOT include undefined
expectTypeOf<typeof schema._output>().toEqualTypeOf<string>();
expectTypeOf<typeof schema._input>().toEqualTypeOf<string>();
});

test("exactOptional unwrap", () => {
const unwrapped = z.string().exactOptional().unwrap();
expect(unwrapped).toBeInstanceOf(z.ZodString);
});

test("exactOptional optionality", () => {
const a = z.string().exactOptional();
expect(a._zod.optin).toEqual("optional");
expect(a._zod.optout).toEqual("optional");
expectTypeOf<typeof a._zod.optin>().toEqualTypeOf<"optional">();
expectTypeOf<typeof a._zod.optout>().toEqualTypeOf<"optional">();
});

test("exactOptional in objects - absent keys", () => {
const schema = z.object({
a: z.string().exactOptional(),
});

// Absent key should pass
expect(schema.parse({})).toEqual({});
expect(schema.parse({}, { jitless: true })).toEqual({});

// Present key with valid value should pass
expect(schema.parse({ a: "hello" })).toEqual({ a: "hello" });
expect(schema.parse({ a: "hello" }, { jitless: true })).toEqual({ a: "hello" });
});

test("exactOptional in objects - explicit undefined rejected", () => {
const schema = z.object({
a: z.string().exactOptional(),
});

// Explicit undefined should fail
expect(schema.safeParse({ a: undefined }).success).toEqual(false);
expect(schema.safeParse({ a: undefined }, { jitless: true }).success).toEqual(false);
});

test("exactOptional type inference in objects", () => {
const schema = z.object({
a: z.string().exactOptional(),
b: z.string().optional(),
});

type SchemaIn = z.input<typeof schema>;
expectTypeOf<SchemaIn>().toEqualTypeOf<{
a?: string;
b?: string | undefined;
}>();

type SchemaOut = z.output<typeof schema>;
expectTypeOf<SchemaOut>().toEqualTypeOf<{
a?: string;
b?: string | undefined;
}>();
});

test("exactOptional vs optional comparison", () => {
const optionalSchema = z.object({ a: z.string().optional() });
const exactOptionalSchema = z.object({ a: z.string().exactOptional() });

// Both accept absent keys
expect(optionalSchema.parse({})).toEqual({});
expect(exactOptionalSchema.parse({})).toEqual({});

// Both accept valid values
expect(optionalSchema.parse({ a: "hi" })).toEqual({ a: "hi" });
expect(exactOptionalSchema.parse({ a: "hi" })).toEqual({ a: "hi" });

// optional() accepts explicit undefined
expect(optionalSchema.parse({ a: undefined })).toEqual({ a: undefined });

// exactOptional() rejects explicit undefined
expect(exactOptionalSchema.safeParse({ a: undefined }).success).toEqual(false);
});
Loading