Skip to content

Commit bc4a7c3

Browse files
committed
Ignore undefined properties
1 parent 7493f4b commit bc4a7c3

File tree

2 files changed

+57
-39
lines changed

2 files changed

+57
-39
lines changed

src/index.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -816,4 +816,32 @@ describe("jsonSchemaObjectToZodRawShape", () => {
816816
// Test refinement with invalid age
817817
expect(() => customSchema.parse({ age: 16 })).toThrow();
818818
});
819+
820+
it("should skip properties with undefined schemas", () => {
821+
const jsonSchema = {
822+
type: "object",
823+
properties: {
824+
name: { type: "string" },
825+
undefinedProp: undefined,
826+
age: { type: "integer" },
827+
anotherUndefined: undefined
828+
},
829+
required: ["name"]
830+
};
831+
832+
const rawShape = jsonSchemaObjectToZodRawShape(jsonSchema);
833+
834+
// Should only have name and age properties
835+
expect(rawShape).toHaveProperty("name");
836+
expect(rawShape).toHaveProperty("age");
837+
expect(rawShape).not.toHaveProperty("undefinedProp");
838+
expect(rawShape).not.toHaveProperty("anotherUndefined");
839+
840+
// Verify correct types
841+
expect(rawShape.name instanceof z.ZodString).toBe(true);
842+
expect(rawShape.age instanceof z.ZodOptional).toBe(true);
843+
844+
// The shape should only have 2 properties
845+
expect(Object.keys(rawShape).length).toBe(2);
846+
});
819847
});

src/index.ts

Lines changed: 29 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,7 @@ export type JSONSchema = {
3737
*/
3838
export function convertJsonSchemaToZod(schema: JSONSchema): z.ZodTypeAny {
3939
// Create a helper function to add metadata like description
40-
function addMetadata(
41-
zodSchema: z.ZodTypeAny,
42-
jsonSchema: JSONSchema,
43-
): z.ZodTypeAny {
40+
function addMetadata(zodSchema: z.ZodTypeAny, jsonSchema: JSONSchema): z.ZodTypeAny {
4441
if (jsonSchema.description) {
4542
zodSchema = zodSchema.describe(jsonSchema.description);
4643
}
@@ -72,11 +69,11 @@ export function convertJsonSchemaToZod(schema: JSONSchema): z.ZodTypeAny {
7269
if (schema.enum.length === 0) {
7370
return addMetadata(z.string(), schema);
7471
}
75-
72+
7673
// Since we know this is a string type, we can safely cast enum values
7774
return addMetadata(z.enum(schema.enum as [string, ...string[]]), schema);
7875
}
79-
76+
8077
let stringSchema = z.string();
8178

8279
// Apply string-specific constraints
@@ -101,22 +98,22 @@ export function convertJsonSchemaToZod(schema: JSONSchema): z.ZodTypeAny {
10198
if (schema.enum.length === 0) {
10299
return addMetadata(z.number(), schema);
103100
}
104-
101+
105102
// For numbers we need a union of literals since z.enum only works with strings
106-
const options = schema.enum.map(val => z.literal(val as number));
107-
103+
const options = schema.enum.map((val) => z.literal(val as number));
104+
108105
// Handle single option enum specially
109106
if (options.length === 1) {
110107
return addMetadata(options[0], schema);
111108
}
112-
109+
113110
// For multiple options, create a union
114111
if (options.length >= 2) {
115112
const unionSchema = z.union([options[0], options[1], ...options.slice(2)]);
116113
return addMetadata(unionSchema, schema);
117114
}
118115
}
119-
116+
120117
let numberSchema = schema.type === "integer" ? z.number().int() : z.number();
121118

122119
// Apply number-specific constraints
@@ -145,14 +142,14 @@ export function convertJsonSchemaToZod(schema: JSONSchema): z.ZodTypeAny {
145142
if (schema.enum.length === 0) {
146143
return addMetadata(z.boolean(), schema);
147144
}
148-
149-
const options = schema.enum.map(val => z.literal(val as boolean));
150-
145+
146+
const options = schema.enum.map((val) => z.literal(val as boolean));
147+
151148
// Handle single option enum specially
152149
if (options.length === 1) {
153150
return addMetadata(options[0], schema);
154151
}
155-
152+
156153
// For multiple options, create a union
157154
if (options.length >= 2) {
158155
const unionSchema = z.union([options[0], options[1], ...options.slice(2)]);
@@ -167,9 +164,7 @@ export function convertJsonSchemaToZod(schema: JSONSchema): z.ZodTypeAny {
167164
const shape: Record<string, z.ZodTypeAny> = {};
168165

169166
// Process each property
170-
for (const [key, propSchema] of Object.entries(
171-
schema.properties,
172-
)) {
167+
for (const [key, propSchema] of Object.entries(schema.properties)) {
173168
shape[key] = convertJsonSchemaToZod(propSchema);
174169
}
175170

@@ -192,7 +187,7 @@ export function convertJsonSchemaToZod(schema: JSONSchema): z.ZodTypeAny {
192187

193188
// Create the schema with or without passthrough based on additionalProperties
194189
let zodSchema: z.ZodTypeAny;
195-
190+
196191
// By default, JSON Schema allows additional properties, so use passthrough
197192
// unless additionalProperties is explicitly set to false
198193
if (schema.additionalProperties !== false) {
@@ -227,11 +222,11 @@ export function convertJsonSchemaToZod(schema: JSONSchema): z.ZodTypeAny {
227222
const seen = new Set();
228223
return items.every((item) => {
229224
// For primitive values, we can use a Set directly
230-
if (typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean') {
225+
if (typeof item === "string" || typeof item === "number" || typeof item === "boolean") {
231226
if (seen.has(item)) return false;
232227
seen.add(item);
233228
return true;
234-
}
229+
}
235230
// For objects, we'd need more complex comparison
236231
// For simplicity, we stringfy objects for comparison
237232
const serialized = JSON.stringify(item);
@@ -240,7 +235,7 @@ export function convertJsonSchemaToZod(schema: JSONSchema): z.ZodTypeAny {
240235
return true;
241236
});
242237
},
243-
{ message: "Array items must be unique" }
238+
{ message: "Array items must be unique" },
244239
);
245240
}
246241

@@ -256,22 +251,22 @@ export function convertJsonSchemaToZod(schema: JSONSchema): z.ZodTypeAny {
256251
if (schema.enum.length === 0) {
257252
return addMetadata(z.never(), schema);
258253
}
259-
254+
260255
// Check if all enum values are strings
261-
const allStrings = schema.enum.every(val => typeof val === 'string');
262-
256+
const allStrings = schema.enum.every((val) => typeof val === "string");
257+
263258
if (allStrings) {
264259
// If all values are strings, use z.enum which is more efficient
265260
return addMetadata(z.enum(schema.enum as [string, ...string[]]), schema);
266261
} else {
267262
// For mixed types or non-strings, use a union of literals
268-
const options = schema.enum.map(val => z.literal(val));
269-
263+
const options = schema.enum.map((val) => z.literal(val));
264+
270265
// Handle single option enum specially
271266
if (options.length === 1) {
272267
return addMetadata(options[0], schema);
273268
}
274-
269+
275270
// For multiple options, create a union
276271
if (options.length >= 2) {
277272
const unionSchema = z.union([options[0], options[1], ...options.slice(2)]);
@@ -283,17 +278,13 @@ export function convertJsonSchemaToZod(schema: JSONSchema): z.ZodTypeAny {
283278
// Handle combinations
284279
if (schema.anyOf && schema.anyOf.length >= 2) {
285280
const schemas = schema.anyOf.map(convertJsonSchemaToZod);
286-
return addMetadata(
287-
z.union([schemas[0], schemas[1], ...schemas.slice(2)]),
288-
schema,
289-
);
281+
return addMetadata(z.union([schemas[0], schemas[1], ...schemas.slice(2)]), schema);
290282
}
291283

292284
if (schema.allOf) {
293285
return addMetadata(
294286
schema.allOf.reduce(
295-
(acc: z.ZodTypeAny, s: JSONSchema) =>
296-
z.intersection(acc, convertJsonSchemaToZod(s)),
287+
(acc: z.ZodTypeAny, s: JSONSchema) => z.intersection(acc, convertJsonSchemaToZod(s)),
297288
z.object({}),
298289
),
299290
schema,
@@ -302,10 +293,7 @@ export function convertJsonSchemaToZod(schema: JSONSchema): z.ZodTypeAny {
302293

303294
if (schema.oneOf && schema.oneOf.length >= 2) {
304295
const schemas = schema.oneOf.map(convertJsonSchemaToZod);
305-
return addMetadata(
306-
z.union([schemas[0], schemas[1], ...schemas.slice(2)]),
307-
schema,
308-
);
296+
return addMetadata(z.union([schemas[0], schemas[1], ...schemas.slice(2)]), schema);
309297
}
310298

311299
// Default fallback
@@ -326,6 +314,8 @@ export function jsonSchemaObjectToZodRawShape(schema: JSONSchema): z.ZodRawShape
326314

327315
// Process each property
328316
for (const [key, propSchema] of Object.entries(schema.properties ?? {})) {
317+
if (propSchema === undefined) continue;
318+
329319
let zodSchema = convertJsonSchemaToZod(propSchema);
330320

331321
// If there's a required array and the field is not in it, make it optional
@@ -343,4 +333,4 @@ export function jsonSchemaObjectToZodRawShape(schema: JSONSchema): z.ZodRawShape
343333
}
344334

345335
return raw;
346-
}
336+
}

0 commit comments

Comments
 (0)