json-schema-to-zod
Version:
Converts JSON schema objects or files into Zod schemas
179 lines (178 loc) • 7.31 kB
JavaScript
import { parseAnyOf } from "./parseAnyOf.js";
import { parseOneOf } from "./parseOneOf.js";
import { its, parseSchema } from "./parseSchema.js";
import { parseAllOf } from "./parseAllOf.js";
import { addJsdocs } from "../utils/jsdocs.js";
export function parseObject(objectSchema, refs) {
let properties = undefined;
if (objectSchema.properties) {
if (!Object.keys(objectSchema.properties).length) {
properties = "z.object({})";
}
else {
properties = "z.object({ ";
properties += Object.keys(objectSchema.properties)
.map((key) => {
const propSchema = objectSchema.properties[key];
let result = `${JSON.stringify(key)}: ${parseSchema(propSchema, {
...refs,
path: [...refs.path, "properties", key],
})}`;
if (refs.withJsdocs && typeof propSchema === "object") {
result = addJsdocs(propSchema, result);
}
const hasDefault = typeof propSchema === "object" && propSchema.default !== undefined;
const required = Array.isArray(objectSchema.required)
? objectSchema.required.includes(key)
: typeof propSchema === "object" && propSchema.required === true;
const optional = !hasDefault && !required;
return optional ? `${result}.optional()` : result;
})
.join(", ");
properties += " })";
}
}
const additionalProperties = objectSchema.additionalProperties !== undefined
? parseSchema(objectSchema.additionalProperties, {
...refs,
path: [...refs.path, "additionalProperties"],
})
: undefined;
let patternProperties = undefined;
if (objectSchema.patternProperties) {
const parsedPatternProperties = Object.fromEntries(Object.entries(objectSchema.patternProperties).map(([key, value]) => {
return [
key,
parseSchema(value, {
...refs,
path: [...refs.path, "patternProperties", key],
}),
];
}, {}));
patternProperties = "";
if (properties) {
if (additionalProperties) {
patternProperties += `.catchall(z.union([${[
...Object.values(parsedPatternProperties),
additionalProperties,
].join(", ")}]))`;
}
else if (Object.keys(parsedPatternProperties).length > 1) {
patternProperties += `.catchall(z.union([${Object.values(parsedPatternProperties).join(", ")}]))`;
}
else {
patternProperties += `.catchall(${Object.values(parsedPatternProperties)})`;
}
}
else {
if (additionalProperties) {
patternProperties += `z.record(z.union([${[
...Object.values(parsedPatternProperties),
additionalProperties,
].join(", ")}]))`;
}
else if (Object.keys(parsedPatternProperties).length > 1) {
patternProperties += `z.record(z.union([${Object.values(parsedPatternProperties).join(", ")}]))`;
}
else {
patternProperties += `z.record(${Object.values(parsedPatternProperties)})`;
}
}
patternProperties += ".superRefine((value, ctx) => {\n";
patternProperties += "for (const key in value) {\n";
if (additionalProperties) {
if (objectSchema.properties) {
patternProperties += `let evaluated = [${Object.keys(objectSchema.properties)
.map((key) => JSON.stringify(key))
.join(", ")}].includes(key)\n`;
}
else {
patternProperties += `let evaluated = false\n`;
}
}
for (const key in objectSchema.patternProperties) {
patternProperties +=
"if (key.match(new RegExp(" + JSON.stringify(key) + "))) {\n";
if (additionalProperties) {
patternProperties += "evaluated = true\n";
}
patternProperties +=
"const result = " +
parsedPatternProperties[key] +
".safeParse(value[key])\n";
patternProperties += "if (!result.success) {\n";
patternProperties += `ctx.addIssue({
path: [...ctx.path, key],
code: 'custom',
message: \`Invalid input: Key matching regex /\${key}/ must match schema\`,
params: {
issues: result.error.issues
}
})\n`;
patternProperties += "}\n";
patternProperties += "}\n";
}
if (additionalProperties) {
patternProperties += "if (!evaluated) {\n";
patternProperties +=
"const result = " + additionalProperties + ".safeParse(value[key])\n";
patternProperties += "if (!result.success) {\n";
patternProperties += `ctx.addIssue({
path: [...ctx.path, key],
code: 'custom',
message: \`Invalid input: must match catchall schema\`,
params: {
issues: result.error.issues
}
})\n`;
patternProperties += "}\n";
patternProperties += "}\n";
}
patternProperties += "}\n";
patternProperties += "})";
}
let output = properties
? patternProperties
? properties + patternProperties
: additionalProperties
? additionalProperties === "z.never()"
? properties + ".strict()"
: properties + `.catchall(${additionalProperties})`
: properties
: patternProperties
? patternProperties
: additionalProperties
? `z.record(${additionalProperties})`
: "z.record(z.any())";
if (its.an.anyOf(objectSchema)) {
output += `.and(${parseAnyOf({
...objectSchema,
anyOf: objectSchema.anyOf.map((x) => typeof x === "object" &&
!x.type &&
(x.properties || x.additionalProperties || x.patternProperties)
? { ...x, type: "object" }
: x),
}, refs)})`;
}
if (its.a.oneOf(objectSchema)) {
output += `.and(${parseOneOf({
...objectSchema,
oneOf: objectSchema.oneOf.map((x) => typeof x === "object" &&
!x.type &&
(x.properties || x.additionalProperties || x.patternProperties)
? { ...x, type: "object" }
: x),
}, refs)})`;
}
if (its.an.allOf(objectSchema)) {
output += `.and(${parseAllOf({
...objectSchema,
allOf: objectSchema.allOf.map((x) => typeof x === "object" &&
!x.type &&
(x.properties || x.additionalProperties || x.patternProperties)
? { ...x, type: "object" }
: x),
}, refs)})`;
}
return output;
}