Skip to content

Commit cf102b5

Browse files
committed
Handle tuples, too
1 parent 75d4c34 commit cf102b5

File tree

2 files changed

+132
-1
lines changed

2 files changed

+132
-1
lines changed

src/index.test.ts

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,135 @@ describe("convertJsonSchemaToZod", () => {
691691
];
692692
expect(() => zodSchema.parse(duplicateObjects)).toThrow();
693693
});
694+
695+
describe("Tuple arrays (items as array)", () => {
696+
it("should handle tuple array with different types", () => {
697+
const jsonSchema = {
698+
$schema: "https://json-schema.org/draft/2020-12/schema",
699+
type: "array",
700+
items: [
701+
{ type: "string" },
702+
{ type: "number" },
703+
{ type: "boolean" }
704+
]
705+
};
706+
707+
const zodSchema = convertJsonSchemaToZod(jsonSchema);
708+
709+
// Valid tuple should pass
710+
expect(() => zodSchema.parse(["hello", 42, true])).not.toThrow();
711+
712+
// Wrong types should fail
713+
expect(() => zodSchema.parse([42, "hello", true])).toThrow();
714+
expect(() => zodSchema.parse(["hello", "world", true])).toThrow();
715+
716+
// Wrong length should fail
717+
expect(() => zodSchema.parse(["hello", 42])).toThrow();
718+
expect(() => zodSchema.parse(["hello", 42, true, "extra"])).toThrow();
719+
});
720+
721+
it("should handle tuple array with single item type", () => {
722+
const jsonSchema = {
723+
$schema: "https://json-schema.org/draft/2020-12/schema",
724+
type: "array",
725+
items: [
726+
{ type: "string" }
727+
]
728+
};
729+
730+
const zodSchema = convertJsonSchemaToZod(jsonSchema);
731+
732+
expect(() => zodSchema.parse(["hello"])).not.toThrow();
733+
expect(() => zodSchema.parse([42])).toThrow();
734+
expect(() => zodSchema.parse(["hello", "world"])).toThrow();
735+
expect(() => zodSchema.parse([])).toThrow();
736+
});
737+
738+
it("should handle empty tuple array", () => {
739+
const jsonSchema = {
740+
$schema: "https://json-schema.org/draft/2020-12/schema",
741+
type: "array",
742+
items: []
743+
};
744+
745+
const zodSchema = convertJsonSchemaToZod(jsonSchema);
746+
747+
expect(() => zodSchema.parse([])).not.toThrow();
748+
expect(() => zodSchema.parse(["anything"])).toThrow();
749+
});
750+
751+
it("should handle tuple array with complex item types", () => {
752+
const jsonSchema = {
753+
$schema: "https://json-schema.org/draft/2020-12/schema",
754+
type: "array",
755+
items: [
756+
{
757+
type: "object",
758+
properties: {
759+
name: { type: "string" }
760+
},
761+
required: ["name"]
762+
},
763+
{ type: "number", minimum: 0 },
764+
{
765+
type: "array",
766+
items: { type: "string" }
767+
}
768+
]
769+
};
770+
771+
const zodSchema = convertJsonSchemaToZod(jsonSchema);
772+
773+
// Valid tuple should pass
774+
expect(() => zodSchema.parse([
775+
{ name: "John" },
776+
5,
777+
["a", "b", "c"]
778+
])).not.toThrow();
779+
780+
// Invalid object should fail
781+
expect(() => zodSchema.parse([
782+
{ age: 25 },
783+
5,
784+
["a", "b", "c"]
785+
])).toThrow();
786+
787+
// Invalid number should fail
788+
expect(() => zodSchema.parse([
789+
{ name: "John" },
790+
-5,
791+
["a", "b", "c"]
792+
])).toThrow();
793+
794+
// Invalid nested array should fail
795+
expect(() => zodSchema.parse([
796+
{ name: "John" },
797+
5,
798+
["a", 123, "c"]
799+
])).toThrow();
800+
});
801+
802+
it("should convert tuple to proper JSON schema", () => {
803+
const jsonSchema = {
804+
$schema: "https://json-schema.org/draft/2020-12/schema",
805+
type: "array",
806+
items: [
807+
{ type: "string" },
808+
{ type: "number" }
809+
]
810+
};
811+
812+
const zodSchema = convertJsonSchemaToZod(jsonSchema);
813+
const resultSchema = z.toJSONSchema(zodSchema);
814+
815+
// Zod converts tuples to use prefixItems (which is correct for draft 2020-12)
816+
expect(resultSchema.type).toEqual("array");
817+
expect(resultSchema.prefixItems).toEqual([
818+
{ type: "string" },
819+
{ type: "number" }
820+
]);
821+
});
822+
});
694823
});
695824
});
696825

src/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,9 @@ export function convertJsonSchemaToZod(schema: JSONSchema.BaseSchema): z.ZodType
178178

179179
let arraySchema;
180180
if (Array.isArray(s.items)) {
181-
throw new Error("FIXME: implement this case, which describes a tuple");
181+
// Handle tuple arrays - items is an array of schemas
182+
const tupleItems = s.items.map(itemSchema => convertJsonSchemaToZod(itemSchema));
183+
arraySchema = z.tuple(tupleItems as [z.ZodTypeAny, ...z.ZodTypeAny[]]);
182184
} else if (s.items) {
183185
arraySchema = z.array(convertJsonSchemaToZod(s.items));
184186
} else {

0 commit comments

Comments
 (0)