Skip to content

Commit 7259bde

Browse files
committed
Respect required in jsonSchemaObjectToZodRawShape
Fixes #11
1 parent 1cc019c commit 7259bde

File tree

2 files changed

+102
-4
lines changed

2 files changed

+102
-4
lines changed

src/index.test.ts

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1018,10 +1018,14 @@ describe("jsonSchemaObjectToZodRawShape", () => {
10181018
expect(rawShape).toHaveProperty("age");
10191019
expect(rawShape).toHaveProperty("isActive");
10201020

1021-
// Verify types are correct
1021+
// Verify types are correct - required fields are direct types
10221022
expect(rawShape.name instanceof z.ZodString).toBe(true);
10231023
expect(rawShape.age instanceof z.ZodNumber).toBe(true);
1024-
expect(rawShape.isActive instanceof z.ZodBoolean).toBe(true);
1024+
1025+
// isActive is not in required array, so it should be optional
1026+
expect(rawShape.isActive instanceof z.ZodOptional).toBe(true);
1027+
// Check the inner type of the optional
1028+
expect((rawShape.isActive as z.ZodOptional<any>)._def.innerType instanceof z.ZodBoolean).toBe(true);
10251029
});
10261030

10271031
it("should handle empty properties", () => {
@@ -1064,7 +1068,10 @@ describe("jsonSchemaObjectToZodRawShape", () => {
10641068
const rawShape = jsonSchemaObjectToZodRawShape(jsonSchema);
10651069

10661070
expect(rawShape).toHaveProperty("user");
1067-
expect(rawShape.user instanceof z.ZodObject).toBe(true);
1071+
// Since there's no required array at the top level, user field is optional
1072+
expect(rawShape.user instanceof z.ZodOptional).toBe(true);
1073+
// The inner type should be a ZodObject
1074+
expect((rawShape.user as z.ZodOptional<any>)._def.innerType instanceof z.ZodObject).toBe(true);
10681075

10691076
// Create a schema with the raw shape to test validation
10701077
const schema = z.object(rawShape);
@@ -1082,6 +1089,11 @@ describe("jsonSchemaObjectToZodRawShape", () => {
10821089
user: { email: "[email protected]" },
10831090
}),
10841091
).toThrow();
1092+
1093+
// Since user is optional at the top level, empty object should pass
1094+
expect(() =>
1095+
schema.parse({}),
1096+
).not.toThrow();
10851097
});
10861098

10871099
it("should be usable to build custom schemas", () => {
@@ -1123,4 +1135,73 @@ describe("jsonSchemaObjectToZodRawShape", () => {
11231135
// Test refinement with invalid age
11241136
expect(() => customSchema.parse({ age: 16 })).toThrow();
11251137
});
1138+
1139+
it("should respect the required field when converting object properties", () => {
1140+
const jsonSchema = {
1141+
type: "object",
1142+
properties: {
1143+
requiredField: { type: "string" },
1144+
optionalField: { type: "number" },
1145+
anotherRequired: { type: "boolean" },
1146+
},
1147+
required: ["requiredField", "anotherRequired"],
1148+
};
1149+
1150+
const rawShape = jsonSchemaObjectToZodRawShape(jsonSchema);
1151+
1152+
// Create a schema to test the shape
1153+
const schema = z.object(rawShape);
1154+
1155+
// Required fields should be required
1156+
expect(() =>
1157+
schema.parse({
1158+
optionalField: 42,
1159+
}),
1160+
).toThrow(); // Missing required fields
1161+
1162+
// Optional field should be optional
1163+
expect(() =>
1164+
schema.parse({
1165+
requiredField: "test",
1166+
anotherRequired: true,
1167+
}),
1168+
).not.toThrow(); // Optional field missing is OK
1169+
1170+
// All fields present should work
1171+
expect(() =>
1172+
schema.parse({
1173+
requiredField: "test",
1174+
optionalField: 42,
1175+
anotherRequired: true,
1176+
}),
1177+
).not.toThrow();
1178+
});
1179+
1180+
it("should make all fields optional when required array is not present", () => {
1181+
const jsonSchema = {
1182+
type: "object",
1183+
properties: {
1184+
field1: { type: "string" },
1185+
field2: { type: "number" },
1186+
field3: { type: "boolean" },
1187+
},
1188+
// No required field - all properties should be optional
1189+
};
1190+
1191+
const rawShape = jsonSchemaObjectToZodRawShape(jsonSchema);
1192+
const schema = z.object(rawShape);
1193+
1194+
// All fields should be optional
1195+
expect(() => schema.parse({})).not.toThrow();
1196+
expect(() => schema.parse({ field1: "test" })).not.toThrow();
1197+
expect(() => schema.parse({ field2: 42 })).not.toThrow();
1198+
expect(() => schema.parse({ field3: true })).not.toThrow();
1199+
expect(() =>
1200+
schema.parse({
1201+
field1: "test",
1202+
field2: 42,
1203+
field3: true,
1204+
}),
1205+
).not.toThrow();
1206+
});
11261207
});

src/index.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,26 @@ export { createUniqueItemsValidator, isValidWithSchema } from "./core/utils";
1616
export function jsonSchemaObjectToZodRawShape(schema: JSONSchema.Schema): z.ZodRawShape {
1717
const raw: Record<string, z.ZodType> = {};
1818

19+
// Get the required fields set for efficient lookup
20+
const requiredFields = new Set(schema.required ?? []);
21+
1922
for (const [key, value] of Object.entries(schema.properties ?? {})) {
2023
if (value === undefined) continue;
21-
raw[key] = convertJsonSchemaToZod(value);
24+
25+
let zodType = convertJsonSchemaToZod(value);
26+
27+
// If there's a required array and the field is not in it, make it optional
28+
// If there's no required array, all fields are optional by default in JSON Schema
29+
if (schema.required !== undefined) {
30+
if (!requiredFields.has(key)) {
31+
zodType = zodType.optional();
32+
}
33+
} else {
34+
// No required array means all fields are optional
35+
zodType = zodType.optional();
36+
}
37+
38+
raw[key] = zodType;
2239
}
2340
return raw;
2441
}

0 commit comments

Comments
 (0)