json-schema-to-zod
Version:
Converts JSON schema objects or files into Zod schemas
140 lines (139 loc) • 4.44 kB
JavaScript
import { parseAnyOf } from "./parseAnyOf.js";
import { parseBoolean } from "./parseBoolean.js";
import { parseDefault } from "./parseDefault.js";
import { parseMultipleType } from "./parseMultipleType.js";
import { parseNot } from "./parseNot.js";
import { parseNull } from "./parseNull.js";
import { parseAllOf } from "./parseAllOf.js";
import { parseArray } from "./parseArray.js";
import { parseConst } from "./parseConst.js";
import { parseEnum } from "./parseEnum.js";
import { parseIfThenElse } from "./parseIfThenElse.js";
import { parseNumber } from "./parseNumber.js";
import { parseObject } from "./parseObject.js";
import { parseString } from "./parseString.js";
import { parseOneOf } from "./parseOneOf.js";
import { parseNullable } from "./parseNullable.js";
export const parseSchema = (schema, refs = { seen: new Map(), path: [] }, blockMeta) => {
if (typeof schema !== "object")
return schema ? "z.any()" : "z.never()";
if (refs.parserOverride) {
const custom = refs.parserOverride(schema, refs);
if (typeof custom === "string") {
return custom;
}
}
let seen = refs.seen.get(schema);
if (seen) {
if (seen.r !== undefined) {
return seen.r;
}
if (refs.depth === undefined || seen.n >= refs.depth) {
return "z.any()";
}
seen.n += 1;
}
else {
seen = { r: undefined, n: 0 };
refs.seen.set(schema, seen);
}
let parsed = selectParser(schema, refs);
if (!blockMeta) {
if (!refs.withoutDescribes) {
parsed = addDescribes(schema, parsed);
}
if (!refs.withoutDefaults) {
parsed = addDefaults(schema, parsed);
}
parsed = addAnnotations(schema, parsed);
}
seen.r = parsed;
return parsed;
};
const addDescribes = (schema, parsed) => {
if (schema.description) {
parsed += `.describe(${JSON.stringify(schema.description)})`;
}
return parsed;
};
const addDefaults = (schema, parsed) => {
if (schema.default !== undefined) {
parsed += `.default(${JSON.stringify(schema.default)})`;
}
return parsed;
};
const addAnnotations = (schema, parsed) => {
if (schema.readOnly) {
parsed += ".readonly()";
}
return parsed;
};
const selectParser = (schema, refs) => {
if (its.a.nullable(schema)) {
return parseNullable(schema, refs);
}
else if (its.an.object(schema)) {
return parseObject(schema, refs);
}
else if (its.an.array(schema)) {
return parseArray(schema, refs);
}
else if (its.an.anyOf(schema)) {
return parseAnyOf(schema, refs);
}
else if (its.an.allOf(schema)) {
return parseAllOf(schema, refs);
}
else if (its.a.oneOf(schema)) {
return parseOneOf(schema, refs);
}
else if (its.a.not(schema)) {
return parseNot(schema, refs);
}
else if (its.an.enum(schema)) {
return parseEnum(schema); //<-- needs to come before primitives
}
else if (its.a.const(schema)) {
return parseConst(schema);
}
else if (its.a.multipleType(schema)) {
return parseMultipleType(schema, refs);
}
else if (its.a.primitive(schema, "string")) {
return parseString(schema);
}
else if (its.a.primitive(schema, "number") ||
its.a.primitive(schema, "integer")) {
return parseNumber(schema);
}
else if (its.a.primitive(schema, "boolean")) {
return parseBoolean(schema);
}
else if (its.a.primitive(schema, "null")) {
return parseNull(schema);
}
else if (its.a.conditional(schema)) {
return parseIfThenElse(schema, refs);
}
else {
return parseDefault(schema);
}
};
export const its = {
an: {
object: (x) => x.type === "object",
array: (x) => x.type === "array",
anyOf: (x) => x.anyOf !== undefined,
allOf: (x) => x.allOf !== undefined,
enum: (x) => x.enum !== undefined,
},
a: {
nullable: (x) => x.nullable === true,
multipleType: (x) => Array.isArray(x.type),
not: (x) => x.not !== undefined,
const: (x) => x.const !== undefined,
primitive: (x, p) => x.type === p,
conditional: (x) => Boolean("if" in x && x.if && "then" in x && "else" in x && x.then && x.else),
oneOf: (x) => x.oneOf !== undefined,
},
};