Skip to content

Commit ccad17a

Browse files
committed
fix(schema-to-json): support serializing Zod 4 z.date() to JSON Schema (#4939)
In Zod 4, toJSONSchema delegates Date schemas to handleUnrepresentable, which threw 'Date cannot be represented in JSON Schema' when unrepresentable was omitted. Configure unrepresentable to return { type: 'string', format: 'date-time' } for date schemas to match Zod 3 output while preserving strict error handling for other unrepresentable types.
1 parent 3dcafbd commit ccad17a

3 files changed

Lines changed: 58 additions & 0 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@trigger.dev/schema-to-json": patch
3+
---
4+
5+
Support serializing Zod 4 `z.date()` schemas to JSON Schema format.

packages/schema-to-json/src/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,12 +156,24 @@ function convertZod4Schema(schema: any, options?: ConversionOptions): JSONSchema
156156
target: "draft-7",
157157
io: "output",
158158
reused: useReferences ? "ref" : "inline",
159+
unrepresentable: ({ zodSchema }) => {
160+
const def = (zodSchema as any)._zod?.def;
161+
if (def?.type === "date") {
162+
return { type: "string", format: "date-time" };
163+
}
164+
return "throw";
165+
},
159166
override: ({ zodSchema, jsonSchema }) => {
160167
const def = zodSchema._zod.def;
161168
if (def.type === "undefined") {
162169
throw new Error("Undefined cannot be represented in JSON Schema");
163170
}
164171

172+
if (def.type === "date") {
173+
jsonSchema.type = "string";
174+
jsonSchema.format = "date-time";
175+
}
176+
165177
if (def.type === "object" && jsonSchema.required) {
166178
// Early Zod 4 permalinks do not propagate optional output through unions.
167179
const required = jsonSchema.required.filter((key) => {

packages/schema-to-json/tests/index.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,45 @@ describe("schemaToJsonSchema", () => {
110110
"Undefined cannot be represented in JSON Schema"
111111
);
112112
});
113+
114+
it("converts date schemas to string with date-time format", () => {
115+
const schema = z.date();
116+
const result = schemaToJsonSchema(schema);
117+
118+
expect(result).toBeDefined();
119+
expect(result?.jsonSchema).toMatchObject({
120+
type: "string",
121+
format: "date-time",
122+
});
123+
});
124+
125+
it("converts object with date schemas and wrappers", () => {
126+
const schema = z.object({
127+
createdAt: z.date(),
128+
updatedAt: z.date().optional(),
129+
deletedAt: z.date().nullable(),
130+
history: z.array(z.date()),
131+
});
132+
133+
const result = schemaToJsonSchema(schema);
134+
135+
expect(result).toBeDefined();
136+
expect(result?.jsonSchema).toMatchObject({
137+
type: "object",
138+
properties: {
139+
createdAt: { type: "string", format: "date-time" },
140+
updatedAt: { type: "string", format: "date-time" },
141+
deletedAt: {
142+
anyOf: [{ type: "string", format: "date-time" }, { type: "null" }],
143+
},
144+
history: {
145+
type: "array",
146+
items: { type: "string", format: "date-time" },
147+
},
148+
},
149+
required: ["createdAt", "deletedAt", "history"],
150+
});
151+
});
113152
});
114153

115154
it("preserves explicitly required metadata on current optional schemas", () => {
@@ -290,6 +329,8 @@ describe("schemaToJsonSchema", () => {
290329
describe("canConvertSchema", () => {
291330
it("should return true for supported schemas", () => {
292331
expect(canConvertSchema(z3.string())).toBe(true);
332+
expect(canConvertSchema(z3.date())).toBe(true);
333+
expect(canConvertSchema(z4.date())).toBe(true);
293334
expect(canConvertSchema(y.string())).toBe(true);
294335
expect(canConvertSchema(type("string"))).toBe(true);
295336
expect(canConvertSchema(Schema.String)).toBe(true);

0 commit comments

Comments
 (0)