UNPKG

json-schema-faker

Version:

Generate valid JSON data from JSON Schema definitions

1,475 lines (1,462 loc) 93.1 kB
#!/usr/bin/env node // src/random.ts function mulberry32(seed) { let s = seed | 0; return () => { s = s + 1831565813 | 0; let t = Math.imul(s ^ s >>> 15, 1 | s); t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t; return ((t ^ t >>> 14) >>> 0) / 4294967296; }; } function createRandom(seed) { const raw = mulberry32(seed); let callCount = 0; const random = { next() { callCount++; return raw(); }, int(min, max) { return min + Math.floor(random.next() * (max - min + 1)); }, bool(probability = 0.5) { return random.next() < probability; }, pick(arr) { return arr[random.int(0, arr.length - 1)]; }, shuffle(arr) { const result = [...arr]; for (let i = result.length - 1;i > 0; i--) { const j = random.int(0, i); [result[i], result[j]] = [result[j], result[i]]; } return result; }, fork() { const newSeed = Math.floor(random.next() * 4294967296); return createRandom(newSeed); } }; return random; } // src/generators/null.ts function generateNull(_ctx) { return null; } // src/generators/boolean.ts function generateBoolean(ctx) { return ctx.random.bool(); } // src/generators/number.ts function generateNumber(schema, ctx, asInteger = false) { let min = -1000; let max = 1000; let exclusiveMin = false; let exclusiveMax = false; if (schema.minimum !== undefined) min = schema.minimum; if (schema.maximum !== undefined) max = schema.maximum; if (schema.exclusiveMinimum !== undefined) { min = schema.exclusiveMinimum; exclusiveMin = true; } if (schema.exclusiveMaximum !== undefined) { max = schema.exclusiveMaximum; exclusiveMax = true; } if (schema.multipleOf !== undefined) { return generateMultipleOf(schema.multipleOf, min, max, exclusiveMin, exclusiveMax, ctx); } if (asInteger) { const lo2 = exclusiveMin ? Math.floor(min) + 1 : Math.ceil(min); const hi2 = exclusiveMax ? Math.ceil(max) - 1 : Math.floor(max); return ctx.random.int(lo2, hi2); } const lo = exclusiveMin ? min + Number.EPSILON : min; const hi = exclusiveMax ? max - Number.EPSILON : max; return lo + ctx.random.next() * (hi - lo); } function generateMultipleOf(multipleOf, min, max, exclusiveMin, exclusiveMax, ctx) { let lo = Math.ceil(min / multipleOf); let hi = Math.floor(max / multipleOf); if (exclusiveMin && lo * multipleOf === min) lo++; if (exclusiveMax && hi * multipleOf === max) hi--; if (lo > hi) return lo * multipleOf; const factor = ctx.random.int(lo, hi); return factor * multipleOf; } // src/generators/integer.ts function generateInteger(schema, ctx) { if (schema.autoIncrement) { const initialOffset = schema.initialOffset ?? 1; const counterKey = getAutoIncrementCounterKey(ctx.path); const counters = ctx.autoIncrementCounters ?? new Map; const currentCount = counters.get(counterKey) ?? initialOffset - 1; const nextValue = currentCount + 1; counters.set(counterKey, nextValue); if (ctx.autoIncrementCounters) { ctx.autoIncrementCounters.set(counterKey, nextValue); } return nextValue; } return generateNumber(schema, ctx, true); } function getAutoIncrementCounterKey(path) { return path.split("/").filter((segment) => segment !== "" && !/^\d+$/.test(segment)).join("/"); } // src/pattern/regex-gen.ts var WORD_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; var DIGIT_CHARS = "0123456789"; var SPACE_CHARS = ` `; var PRINTABLE = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#%&*()-_=+[]{}|;:',.<>/? "; function generateFromRegex(pattern, random) { const ast = parse(pattern, 0); return generate(ast.node, random); } function generate(node, random) { switch (node.type) { case "literal": return node.value; case "dot": return random.pick([...PRINTABLE]); case "anchor": return ""; case "charClass": return generateCharClass(node, random); case "sequence": return node.nodes.map((n) => generate(n, random)).join(""); case "alternation": return generate(random.pick(node.branches), random); case "quantifier": { const count = random.int(node.min, Math.min(node.max, node.min + 5)); return Array.from({ length: count }, () => generate(node.node, random)).join(""); } case "group": return generate(node.node, random); } } function generateCharClass(node, random) { if (!node.negated) { return random.pick(node.chars); } const excluded = new Set(node.chars); const allowed = [...PRINTABLE].filter((c) => !excluded.has(c)); return allowed.length > 0 ? random.pick(allowed) : "a"; } function parse(pattern, pos) { const branches = []; let current = []; while (pos < pattern.length) { const ch = pattern[pos]; if (ch === ")") break; if (ch === "|") { branches.push(seq(current)); current = []; pos++; continue; } if (ch === "(") { let groupStart = pos + 1; if (pattern[groupStart] === "?") { if (pattern[groupStart + 1] === ":") { groupStart += 2; } else if (pattern[groupStart + 1] === "=" || pattern[groupStart + 1] === "!") { let depth = 1; let p = groupStart; while (p < pattern.length && depth > 0) { p++; if (pattern[p] === "(") depth++; if (pattern[p] === ")") depth--; } pos = p + 1; continue; } else { groupStart += 2; while (groupStart < pattern.length && pattern[groupStart] !== ">") groupStart++; groupStart++; } } const inner = parse(pattern, groupStart); const groupNode = { type: "group", node: inner.node }; pos = inner.pos + 1; const qResult2 = tryQuantifier(pattern, pos, groupNode); current.push(qResult2.node); pos = qResult2.pos; continue; } if (ch === "[") { const ccResult = parseCharClass(pattern, pos); const qResult2 = tryQuantifier(pattern, ccResult.pos, ccResult.node); current.push(qResult2.node); pos = qResult2.pos; continue; } if (ch === ".") { const dotNode = { type: "dot" }; pos++; const qResult2 = tryQuantifier(pattern, pos, dotNode); current.push(qResult2.node); pos = qResult2.pos; continue; } if (ch === "^" || ch === "$") { current.push({ type: "anchor" }); pos++; continue; } if (ch === "\\") { pos++; const escaped = parseEscape(pattern, pos); pos = escaped.pos; const qResult2 = tryQuantifier(pattern, pos, escaped.node); current.push(qResult2.node); pos = qResult2.pos; continue; } const litNode = { type: "literal", value: ch }; pos++; const qResult = tryQuantifier(pattern, pos, litNode); current.push(qResult.node); pos = qResult.pos; } branches.push(seq(current)); const node = branches.length === 1 ? branches[0] : { type: "alternation", branches }; return { node, pos }; } function seq(nodes) { if (nodes.length === 0) return { type: "literal", value: "" }; if (nodes.length === 1) return nodes[0]; return { type: "sequence", nodes }; } function parseEscape(pattern, pos) { const ch = pattern[pos]; pos++; switch (ch) { case "d": return { node: { type: "charClass", chars: [...DIGIT_CHARS], negated: false }, pos }; case "D": return { node: { type: "charClass", chars: [...DIGIT_CHARS], negated: true }, pos }; case "w": return { node: { type: "charClass", chars: [...WORD_CHARS], negated: false }, pos }; case "W": return { node: { type: "charClass", chars: [...WORD_CHARS], negated: true }, pos }; case "s": return { node: { type: "charClass", chars: [...SPACE_CHARS], negated: false }, pos }; case "S": return { node: { type: "charClass", chars: [...SPACE_CHARS], negated: true }, pos }; case "b": case "B": return { node: { type: "anchor" }, pos }; default: return { node: { type: "literal", value: ch }, pos }; } } function parseCharClass(pattern, pos) { pos++; let negated = false; if (pattern[pos] === "^") { negated = true; pos++; } const chars = []; while (pos < pattern.length && pattern[pos] !== "]") { if (pattern[pos] === "\\" && pos + 1 < pattern.length) { pos++; const ch = pattern[pos]; switch (ch) { case "d": chars.push(...DIGIT_CHARS); break; case "w": chars.push(...WORD_CHARS); break; case "s": chars.push(...SPACE_CHARS); break; default: chars.push(ch); } pos++; continue; } if (pos + 2 < pattern.length && pattern[pos + 1] === "-" && pattern[pos + 2] !== "]") { const start = pattern.charCodeAt(pos); const end = pattern.charCodeAt(pos + 2); for (let c = start;c <= end; c++) { chars.push(String.fromCharCode(c)); } pos += 3; continue; } chars.push(pattern[pos]); pos++; } pos++; return { node: { type: "charClass", chars, negated }, pos }; } function tryQuantifier(pattern, pos, node) { if (pos >= pattern.length) return { node, pos }; const ch = pattern[pos]; let min; let max; switch (ch) { case "*": min = 0; max = 5; pos++; break; case "+": min = 1; max = 5; pos++; break; case "?": min = 0; max = 1; pos++; break; case "{": { const result = parseBraceQuantifier(pattern, pos); if (result) { min = result.min; max = result.max; pos = result.pos; } else { return { node, pos }; } break; } default: return { node, pos }; } if (pos < pattern.length && (pattern[pos] === "?" || pattern[pos] === "+")) { pos++; } return { node: { type: "quantifier", node, min, max }, pos }; } function parseBraceQuantifier(pattern, pos) { const match = pattern.slice(pos).match(/^\{(\d+)(?:,(\d*))?\}/); if (!match) return null; const min = parseInt(match[1], 10); let max; if (match[2] === undefined) { max = min; } else if (match[2] === "") { max = min + 5; } else { max = parseInt(match[2], 10); } return { min, max, pos: pos + match[0].length }; } // src/utils/helpers.ts function pad2(n) { return n < 10 ? `0${n}` : `${n}`; } function daysInMonth(year, month) { return new Date(year, month, 0).getDate(); } // src/generators/string.ts var DEFAULT_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; function canPatternMatchNonEmpty(pattern) { if (pattern === "^" || pattern === "$" || pattern === "^$") { return false; } return true; } function generateString(schema, ctx) { if (schema.format) { if (schema.format === "date-time" && (ctx.minDateTime !== undefined || ctx.maxDateTime !== undefined)) { return generateDateTimeWithRange(ctx); } const formatGen = ctx.formatRegistry.get(schema.format); if (formatGen) { const result2 = formatGen(ctx.random); const effectiveMinLength = ctx.minLength ?? schema.minLength; const effectiveMaxLength = ctx.maxLength ?? schema.maxLength; if (effectiveMinLength !== undefined && result2.length < effectiveMinLength) { return padString(result2, effectiveMinLength, ctx); } if (effectiveMaxLength !== undefined && result2.length > effectiveMaxLength) { return result2.slice(0, effectiveMaxLength); } return result2; } } if (schema.pattern) { const effectiveMinLength = ctx.minLength ?? schema.minLength; const effectiveMaxLength = ctx.maxLength ?? schema.maxLength; const maxAttempts = 50; for (let attempt = 0;attempt < maxAttempts; attempt++) { const result3 = generateFromRegex(schema.pattern, ctx.random); if (effectiveMinLength !== undefined && result3.length < effectiveMinLength) { continue; } if (effectiveMaxLength !== undefined && result3.length > effectiveMaxLength) { continue; } return result3; } const result2 = generateFromRegex(schema.pattern, ctx.random); const patternCanMatchNonEmpty = canPatternMatchNonEmpty(schema.pattern); if (effectiveMinLength !== undefined && result2.length < effectiveMinLength) { if (patternCanMatchNonEmpty) { return padString(result2, effectiveMinLength, ctx); } } if (effectiveMaxLength !== undefined && result2.length > effectiveMaxLength) { return result2.slice(0, effectiveMaxLength); } return result2; } const minLen = schema.minLength ?? 0; const maxLen = schema.maxLength ?? Math.max(minLen + 10, 10); const length = ctx.random.int(minLen, maxLen); let result = ""; for (let i = 0;i < length; i++) { result += ctx.random.pick([...DEFAULT_CHARS]); } if (ctx.maxLength !== undefined && result.length > ctx.maxLength) { result = result.slice(0, ctx.maxLength); } if (ctx.minLength !== undefined && result.length < ctx.minLength) { result = padString(result, ctx.minLength, ctx); } return result; } function padString(str, targetLength, ctx) { while (str.length < targetLength) { str += ctx.random.pick([...DEFAULT_CHARS]); } return str; } function generateDateTimeWithRange(ctx) { const minDt = ctx.minDateTime ? new Date(ctx.minDateTime) : new Date("1970-01-01"); const maxDt = ctx.maxDateTime ? new Date(ctx.maxDateTime) : new Date; const minTime = minDt.getTime(); const maxTime = maxDt.getTime(); const randomTime = minTime + ctx.random.next() * (maxTime - minTime); const date = new Date(randomTime); const year = date.getFullYear(); const month = pad2(date.getMonth() + 1); const day = pad2(date.getDate()); return `${year}-${month}-${day}T01:01:01.0Z`; } // src/generators/enum-const.ts function generateEnumConst(schema, ctx) { if (schema.const !== undefined) { return structuredClone(schema.const); } if (schema.enum !== undefined && schema.enum.length > 0) { let validValues = schema.enum; if (schema.minimum !== undefined || schema.maximum !== undefined || schema.exclusiveMinimum !== undefined || schema.exclusiveMaximum !== undefined || schema.multipleOf !== undefined) { const min = schema.exclusiveMinimum ?? schema.minimum ?? -Infinity; const max = schema.exclusiveMaximum ?? schema.maximum ?? Infinity; const isExclusiveMin = schema.exclusiveMinimum !== undefined; const isExclusiveMax = schema.exclusiveMaximum !== undefined; validValues = validValues.filter((v) => { if (typeof v !== "number") return true; const aboveMin = isExclusiveMin ? v > min : v >= min; const belowMax = isExclusiveMax ? v < max : v <= max; if (!aboveMin || !belowMax) return false; if (schema.multipleOf !== undefined) { const multipleOf = schema.multipleOf; const decimalPlaces = multipleOf.toString().includes(".") ? multipleOf.toString().split(".")[1].length : 0; const precision = Math.pow(10, decimalPlaces); const scaledVal = Math.round(v * precision); const scaledMultipleOf = Math.round(multipleOf * precision); if (scaledVal % scaledMultipleOf !== 0) return false; } return true; }); } if (schema.minLength !== undefined || schema.maxLength !== undefined) { const minLen = schema.minLength ?? 0; const maxLen = schema.maxLength ?? Infinity; validValues = validValues.filter((v) => { if (typeof v !== "string") return true; return v.length >= minLen && v.length <= maxLen; }); } if (schema.minItems !== undefined || schema.maxItems !== undefined) { const minItems = schema.minItems ?? 0; const maxItems = schema.maxItems ?? Infinity; validValues = validValues.filter((v) => { if (!Array.isArray(v)) return true; return v.length >= minItems && v.length <= maxItems; }); } if (schema.minProperties !== undefined || schema.maxProperties !== undefined) { const minProps = schema.minProperties ?? 0; const maxProps = schema.maxProperties ?? Infinity; validValues = validValues.filter((v) => { if (typeof v !== "object" || v === null || Array.isArray(v)) return true; return Object.keys(v).length >= minProps && Object.keys(v).length <= maxProps; }); } if (validValues.length === 0) { const constraints = []; if (schema.minLength !== undefined) constraints.push(`minLength: ${schema.minLength}`); if (schema.maxLength !== undefined) constraints.push(`maxLength: ${schema.maxLength}`); if (schema.minItems !== undefined) constraints.push(`minItems: ${schema.minItems}`); if (schema.maxItems !== undefined) constraints.push(`maxItems: ${schema.maxItems}`); if (schema.minProperties !== undefined) constraints.push(`minProperties: ${schema.minProperties}`); if (schema.maxProperties !== undefined) constraints.push(`maxProperties: ${schema.maxProperties}`); if (schema.minimum !== undefined) constraints.push(`minimum: ${schema.minimum}`); if (schema.maximum !== undefined) constraints.push(`maximum: ${schema.maximum}`); if (schema.exclusiveMinimum !== undefined) constraints.push(`exclusiveMinimum: ${schema.exclusiveMinimum}`); if (schema.exclusiveMaximum !== undefined) constraints.push(`exclusiveMaximum: ${schema.exclusiveMaximum}`); if (schema.multipleOf !== undefined) constraints.push(`multipleOf: ${schema.multipleOf}`); throw new Error(`enum values conflict with constraints [${constraints.join(", ")}] at ${ctx.path}`); } const value = ctx.random.pick(validValues); return typeof value === "object" && value !== null ? structuredClone(value) : value; } throw new Error(`generateEnumConst called without enum or const at ${ctx.path}`); } // src/merge.ts function mergeSchemas(schemas) { const result = {}; for (const schema of schemas) { if (typeof schema === "boolean") { if (!schema) return { not: {} }; continue; } mergePairInto(result, schema); } return result; } function mergePair(a, b) { if (typeof a === "boolean" || typeof b === "boolean") { return {}; } if (typeof a !== "object" || a === null) { return typeof b === "object" && b !== null ? b : {}; } if (typeof b !== "object" || b === null) { return a; } const result = { ...a }; mergePairInto(result, b); return result; } function mergePairInto(target, source) { if (source.type !== undefined) { if (target.type === undefined) { target.type = source.type; } else { target.type = intersectTypes(target.type, source.type); } } if (source.minimum !== undefined) { target.minimum = target.minimum !== undefined ? Math.max(target.minimum, source.minimum) : source.minimum; } if (source.maximum !== undefined) { target.maximum = target.maximum !== undefined ? Math.min(target.maximum, source.maximum) : source.maximum; } if (source.exclusiveMinimum !== undefined) { target.exclusiveMinimum = target.exclusiveMinimum !== undefined ? Math.max(target.exclusiveMinimum, source.exclusiveMinimum) : source.exclusiveMinimum; } if (source.exclusiveMaximum !== undefined) { target.exclusiveMaximum = target.exclusiveMaximum !== undefined ? Math.min(target.exclusiveMaximum, source.exclusiveMaximum) : source.exclusiveMaximum; } if (source.multipleOf !== undefined) { target.multipleOf = source.multipleOf; } if (source.minLength !== undefined) { target.minLength = target.minLength !== undefined ? Math.max(target.minLength, source.minLength) : source.minLength; } if (source.maxLength !== undefined) { target.maxLength = target.maxLength !== undefined ? Math.min(target.maxLength, source.maxLength) : source.maxLength; } if (source.pattern !== undefined) { target.pattern = source.pattern; } if (source.format !== undefined) { target.format = source.format; } if (source.minItems !== undefined) { target.minItems = target.minItems !== undefined ? Math.max(target.minItems, source.minItems) : source.minItems; } if (source.maxItems !== undefined) { target.maxItems = target.maxItems !== undefined ? Math.min(target.maxItems, source.maxItems) : source.maxItems; } if (source.uniqueItems) target.uniqueItems = true; if (source.items !== undefined) target.items = source.items; if (source.prefixItems !== undefined) { if (target.prefixItems === undefined) { target.prefixItems = source.prefixItems; } else { const merged = []; const maxLen = Math.max(target.prefixItems.length, source.prefixItems.length); for (let i = 0;i < maxLen; i++) { const targetItem = target.prefixItems[i]; const sourceItem = source.prefixItems[i]; if (targetItem !== undefined && sourceItem !== undefined) { merged.push(mergePair(targetItem, sourceItem)); } else if (targetItem !== undefined) { merged.push(targetItem); } else if (sourceItem !== undefined) { merged.push(sourceItem); } } target.prefixItems = merged; } } if (source.contains !== undefined) { if (target.containsAll !== undefined) { target.containsAll = [...target.containsAll, source.contains]; } else if (target.contains !== undefined) { target.containsAll = [target.contains, source.contains]; delete target.contains; } else { target.contains = source.contains; } } if (source.properties) { target.properties = deepMergeProperties(target.properties ?? {}, source.properties); } if (source.required) { target.required = [...new Set([...target.required ?? [], ...source.required])]; } if (source.additionalProperties !== undefined) { target.additionalProperties = source.additionalProperties; } if (source.patternProperties) { target.patternProperties = { ...target.patternProperties ?? {}, ...source.patternProperties }; } if (source.minProperties !== undefined) { target.minProperties = target.minProperties !== undefined ? Math.max(target.minProperties, source.minProperties) : source.minProperties; } if (source.maxProperties !== undefined) { target.maxProperties = target.maxProperties !== undefined ? Math.min(target.maxProperties, source.maxProperties) : source.maxProperties; } if (source.const !== undefined) target.const = source.const; if (source.enum !== undefined) { if (target.enum !== undefined) { const sourceSet = new Set(source.enum.map((v) => JSON.stringify(v))); target.enum = target.enum.filter((v) => sourceSet.has(JSON.stringify(v))); } else { target.enum = source.enum; } } if (source.$ref) target.$ref = source.$ref; if (source.$defs) target.$defs = { ...target.$defs ?? {}, ...source.$defs }; if (source.allOf) { target.allOf = [...target.allOf ?? [], ...source.allOf]; } if (source.anyOf) target.anyOf = source.anyOf; if (source.oneOf) target.oneOf = source.oneOf; } function intersectTypes(a, b) { const aArr = Array.isArray(a) ? a : [a]; const bArr = Array.isArray(b) ? b : [b]; const intersection = aArr.filter((t) => bArr.includes(t)); if (intersection.length === 0) return aArr[0]; if (intersection.length === 1) return intersection[0]; return intersection; } function deepMergeProperties(target, source) { const result = { ...target }; for (const [key, sourceSchema] of Object.entries(source)) { const targetSchema = target[key]; if (targetSchema === undefined) { result[key] = sourceSchema; } else if (typeof targetSchema === "object" && typeof sourceSchema === "object") { result[key] = mergePair(targetSchema, sourceSchema); } else { result[key] = sourceSchema; } } return result; } // src/utils/schema-keywords.ts var SCHEMA_KEYWORDS = new Set([ "$schema", "$id", "$ref", "$defs", "$anchor", "$dynamicRef", "$dynamicAnchor", "$vocabulary", "$comment", "type", "enum", "const", "minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum", "multipleOf", "minLength", "maxLength", "pattern", "format", "contentEncoding", "contentMediaType", "contentSchema", "items", "prefixItems", "contains", "minItems", "maxItems", "uniqueItems", "minContains", "maxContains", "properties", "required", "additionalProperties", "patternProperties", "minProperties", "maxProperties", "propertyNames", "dependentRequired", "dependentSchemas", "allOf", "anyOf", "oneOf", "not", "if", "then", "else", "faker", "chance", "jsonPath", "template", "default", "examples", "description", "title", "readOnly", "writeOnly", "deprecated" ]); function isJsonSchema(value) { if (typeof value !== "object" || value === null) { return false; } const obj = value; return Object.keys(obj).some((key) => SCHEMA_KEYWORDS.has(key)); } // src/generators/object.ts function matchesConstraint(value, constraint) { if (typeof constraint !== "object" || constraint === null) { return false; } const constraintObj = constraint; if (constraintObj.const !== undefined) { return value === constraintObj.const; } if (constraintObj.enum !== undefined) { return constraintObj.enum.some((v) => v === value); } return false; } function isImpossibleSchema(schema) { if (typeof schema === "boolean") { return !schema; } if (typeof schema !== "object" || schema === null) { return false; } if (schema.not === true || typeof schema.not === "object" && schema.not !== null && Object.keys(schema.not).length === 0) { return true; } return false; } function getInferredProperties(schema) { if (schema.properties) { return schema.properties; } if (schema.type === "object" || schema.type === undefined) { const inferred = {}; let hasInferredProperties = false; for (const [key, value] of Object.entries(schema)) { if (SCHEMA_KEYWORDS.has(key)) { continue; } if (isJsonSchema(value)) { inferred[key] = value; hasInferredProperties = true; } else { inferred[key] = { const: value }; hasInferredProperties = true; } } if (hasInferredProperties) { return inferred; } } return {}; } function getInferredRequired(schema, inferredProperties) { if (schema.required) { return schema.required; } if (schema.properties === undefined && Object.keys(inferredProperties).length > 0) { return Object.keys(inferredProperties); } return []; } async function generateObject(schema, ctx) { const inferredProperties = getInferredProperties(schema); const inferredRequired = getInferredRequired(schema, inferredProperties); if (ctx.depth >= ctx.maxDepth) { if (inferredRequired.length === 0) { return {}; } const result2 = {}; for (const key of inferredRequired) { const propSchema = inferredProperties[key]; if (propSchema && typeof propSchema === "object" && propSchema !== null) { const schemaObj = propSchema; if (schemaObj.const !== undefined) { result2[key] = schemaObj.const; } else if (schemaObj.default !== undefined) { result2[key] = schemaObj.default; } else if (schemaObj.enum !== undefined && schemaObj.enum.length > 0) { result2[key] = schemaObj.enum[0]; } else { result2[key] = generateStubValue(schemaObj.type); } } else { result2[key] = null; } } return result2; } if (schema.additionalProperties === false) { const hasProperties = Object.keys(inferredProperties).length > 0; const hasPatternProperties = schema.patternProperties && Object.keys(schema.patternProperties).length > 0; const definedKeys2 = new Set(Object.keys(inferredProperties)); const required2 = new Set(inferredRequired); const requiredKeys = [...required2].filter((k) => !definedKeys2.has(k)); if ((schema.minProperties ?? 0) > 0 && !hasProperties && !hasPatternProperties) { throw new Error(`missing properties for ${schema.minProperties} in ${ctx.path}: additionalProperties is false but no properties or patternProperties defined`); } if (requiredKeys.length > 0 && !hasPatternProperties && !hasProperties) { throw new Error(`missing properties for ${requiredKeys.join(", ")} in ${ctx.path}: additionalProperties is false but required properties not defined in properties`); } } const childCtxPath = ctx.path === "/" ? "/properties" : `${ctx.path}/properties`; const childCtx = { ...ctx, depth: ctx.depth + 1, path: childCtxPath }; const result = {}; const definedKeys = new Set; const required = new Set(inferredRequired); const alwaysFakeOptionals = ctx.alwaysFakeOptionals ?? false; const fillProperties = ctx.fillProperties ?? true; const useFixedProbabilities = ctx.fixedProbabilities ?? false; const optionalsProbability = ctx.optionalsProbability ?? 0.5; const optionalKeys = Object.keys(inferredProperties).filter((key) => !required.has(key)); let propertiesToInclude; if (useFixedProbabilities && !alwaysFakeOptionals && optionalKeys.length > 0) { const targetCount = Math.round(optionalKeys.length * optionalsProbability); propertiesToInclude = new Set(optionalKeys.slice(0, targetCount)); } const shouldGenerateOptional = (key) => { if (alwaysFakeOptionals) return true; if (propertiesToInclude) return propertiesToInclude.has(key); return ctx.random.bool(optionalsProbability); }; if (Object.keys(inferredProperties).length > 0) { for (const [key, propSchema] of Object.entries(inferredProperties)) { if (isImpossibleSchema(propSchema)) { continue; } definedKeys.add(key); const propCtx = createPropertyContext(childCtx, key); const isRequired = required.has(key); const isObjectType = typeof propSchema === "object" && propSchema !== null && propSchema.type === "object"; let mergedPropSchema = propSchema; if (schema.patternProperties && typeof propSchema === "object" && propSchema !== null) { const matchingPatterns = []; for (const [pattern, patSchema] of Object.entries(schema.patternProperties)) { try { if (new RegExp(pattern).test(key)) { matchingPatterns.push(patSchema); } } catch {} } if (matchingPatterns.length > 0) { mergedPropSchema = mergeSchemas([propSchema, ...matchingPatterns]); } } if (isRequired) { const value = await walk(mergedPropSchema, propCtx); if (value !== undefined) { result[key] = value; } } else if (shouldGenerateOptional(key)) { const value = await walk(mergedPropSchema, propCtx); if (value !== undefined) { result[key] = value; } } else if (isObjectType && !fillProperties) { const nestedRequired = propSchema.required ?? []; if (nestedRequired.length > 0) { const nestedResult = {}; for (const nestedKey of nestedRequired) { const nestedSchema = (propSchema.properties ?? {})[nestedKey] ?? {}; const nestedCtx = createPropertyContext(propCtx, nestedKey); const nestedValue = await walk(nestedSchema, nestedCtx); if (nestedValue !== undefined) { nestedResult[nestedKey] = nestedValue; } } result[key] = nestedResult; } } } } for (const key of required) { if (result[key] !== undefined || definedKeys.has(key)) continue; if (schema.patternProperties) { const matchingSchemas = []; for (const [pattern, patSchema] of Object.entries(schema.patternProperties)) { if (new RegExp(pattern).test(key)) { matchingSchemas.push(patSchema); } } if (matchingSchemas.length > 0) { const mergedSchema = matchingSchemas.length === 1 ? matchingSchemas[0] : mergeSchemas(matchingSchemas); const propCtx = createPropertyContext(childCtx, key); const value = await walk(mergedSchema, propCtx); if (value !== undefined) { result[key] = value; } definedKeys.add(key); } } if (!definedKeys.has(key)) { const addlSchema = schema.additionalProperties ?? true; if (addlSchema !== false) { const propCtx = createPropertyContext(childCtx, key); const value = await walk(addlSchema === true ? {} : addlSchema, propCtx); if (value !== undefined) { result[key] = value; } definedKeys.add(key); } } } if (schema.dependencies) { for (const [propName, dependency] of Object.entries(schema.dependencies)) { if (typeof dependency !== "object" || dependency === null || Array.isArray(dependency)) { continue; } if (result[propName] !== undefined && !definedKeys.has(propName + "_dep")) { definedKeys.add(propName + "_dep"); const branches = dependency.oneOf ?? dependency.anyOf ?? dependency.allOf ?? [dependency]; let matchingBranch = null; for (const branch of branches) { if (typeof branch !== "object" || branch === null) continue; const branchProps = branch.properties; const branchNot = branch.not; if (branchProps && branchProps[propName]) { const constraint = branchProps[propName]; const generatedValue = result[propName]; if (matchesConstraint(generatedValue, constraint)) { matchingBranch = branch; break; } } if (branchNot?.properties) { const notPropConstraints = branchNot.properties[propName]; if (notPropConstraints) { const generatedValue = result[propName]; if (!matchesConstraint(generatedValue, notPropConstraints)) { matchingBranch = branch; break; } } } } if (!matchingBranch && branches.length > 0 && typeof branches[0] === "object") { matchingBranch = branches[0]; } if (matchingBranch?.not) { const notSchema = matchingBranch.not; if (notSchema.required && Array.isArray(notSchema.required)) { for (const forbiddenProp of notSchema.required) { delete result[forbiddenProp]; } } } if (matchingBranch?.required) { for (const reqProp of matchingBranch.required) { if (reqProp === propName) continue; if (result[reqProp] !== undefined) continue; const reqPropSchema = matchingBranch.properties?.[reqProp] ?? { type: "object" }; const reqPropCtx = createPropertyContext(childCtx, reqProp); const value = await walk(reqPropSchema, reqPropCtx); if (value !== undefined) { result[reqProp] = value; } } } if (matchingBranch && matchingBranch.properties) { for (const [depPropName, depPropSchema] of Object.entries(matchingBranch.properties)) { if (depPropName === propName) continue; const depPropCtx = createPropertyContext(childCtx, depPropName); const value = await walk(depPropSchema, depPropCtx); if (value !== undefined) { result[depPropName] = value; } } } } } } const minProps = schema.minProperties ?? 0; const maxProps = schema.maxProperties; if (Object.keys(result).length < minProps) { if (schema.patternProperties && Object.keys(schema.patternProperties).length > 0) { const patterns = Object.entries(schema.patternProperties); let attempts = 0; const maxAttempts = 1000; while (Object.keys(result).length < minProps && attempts < maxAttempts) { attempts++; const [pattern, patSchema] = ctx.random.pick(patterns); const key = generateKeyMatchingPattern(pattern, ctx); if (key && !result[key]) { const propCtx = createPropertyContext(childCtx, key); const value = await walk(patSchema, propCtx); if (value !== undefined) { result[key] = value; } definedKeys.add(key); } } } if (Object.keys(result).length < minProps) { const addlSchema = schema.additionalProperties; const genSchema = addlSchema === undefined || addlSchema === true ? { type: "string" } : addlSchema; if (genSchema !== false) { let attempts = 0; const maxAttempts = 1000; while (Object.keys(result).length < minProps && attempts < maxAttempts) { attempts++; const key = `prop${ctx.random.int(0, 99999)}`; if (!result[key]) { const propCtx = createPropertyContext(childCtx, key); const value = await walk(genSchema, propCtx); if (value !== undefined) { result[key] = value; } definedKeys.add(key); } } } } if (Object.keys(result).length < minProps) { const definedPropsList = Object.keys(inferredProperties).join(", "); throw new Error(`properties '${definedPropsList}' were not found while additionalProperties is false in ${ctx.path}`); } } if (maxProps !== undefined && Object.keys(result).length > maxProps) { const keys = Object.keys(result); const toRemove = keys.filter((k) => !required.has(k)); ctx.random.shuffle(toRemove); while (Object.keys(result).length > maxProps && toRemove.length > 0) { const key = toRemove.pop(); delete result[key]; } } if (ctx.pruneProperties && ctx.pruneProperties.length > 0) { pruneObjectProperties(result, ctx.pruneProperties); } resolveTemplates(result, inferredProperties); return result; } function createPropertyContext(ctx, key) { const encodedKey = encodeJsonPointerSegment(key); const outputPath = ctx.outputPath === "/" ? `/${encodedKey}` : `${ctx.outputPath}/${encodedKey}`; return { ...ctx, path: `${ctx.path}/${key}`, outputPath }; } function encodeJsonPointerSegment(segment) { return segment.replace(/~/g, "~0").replace(/\//g, "~1"); } function resolveTemplates(result, properties) { for (const [key, propSchema] of Object.entries(properties)) { if (typeof propSchema !== "object" || propSchema === null) continue; const schema = propSchema; if (schema.template === undefined) continue; const template = schema.template; if (typeof template !== "string") continue; const resolved = template.replace(/#\{(\w+)\}/g, (_, propName) => { const value = result[propName]; return value !== undefined ? String(value) : ""; }); result[key] = resolved; } } function generateKeyMatchingPattern(pattern, ctx) { const prefixMatch = pattern.match(/^\^([a-zA-Z]+)(\.\*|\(\?\:.*\))?/); if (prefixMatch) { const prefix = prefixMatch[1]; const suffix = prefixMatch[2]; if (suffix === ".*" || suffix?.includes("?:")) { const suffixLen = ctx.random.int(0, 10); const chars2 = "abcdefghijklmnopqrstuvwxyz"; let randomSuffix = ""; for (let i = 0;i < suffixLen; i++) { randomSuffix += ctx.random.pick(chars2.split("")); } return prefix + randomSuffix; } return prefix; } const suffixMatch = pattern.match(/(.*\*)?([a-zA-Z]+)\$$/); if (suffixMatch) { const suffix = suffixMatch[2]; const prefixLen = ctx.random.int(1, 10); const chars2 = "abcdefghijklmnopqrstuvwxyz"; let randomPrefix = ""; for (let i = 0;i < prefixLen; i++) { randomPrefix += ctx.random.pick(chars2.split("")); } return randomPrefix + suffix; } const exactMatch = pattern.match(/^\^([a-zA-Z]+)\$$/); if (exactMatch) { return exactMatch[1]; } const len = ctx.random.int(3, 10); const chars = "abcdefghijklmnopqrstuvwxyz"; let key = ""; for (let i = 0;i < len; i++) { key += ctx.random.pick(chars.split("")); } try { if (new RegExp(pattern).test(key)) { return key; } } catch {} return null; } function pruneObjectProperties(obj, propertiesToPrune) { if (typeof obj !== "object" || obj === null) { return; } if (Array.isArray(obj)) { for (const item of obj) { pruneObjectProperties(item, propertiesToPrune); } return; } const record = obj; for (const key of Object.keys(record)) { if (propertiesToPrune.includes(key)) { delete record[key]; } else { pruneObjectProperties(record[key], propertiesToPrune); } } } function generateStubValue(type) { const resolvedType = Array.isArray(type) ? type[0] : type; switch (resolvedType) { case "string": return ""; case "number": case "integer": return 0; case "boolean": return false; case "null": return null; case "array": return []; case "object": return {}; default: return null; } } // src/generators/array.ts async function generateArray(schema, ctx) { if (ctx.depth >= ctx.maxDepth) { const needed = schema.minItems ?? 0; return needed > 0 ? Array.from({ length: needed }, () => null) : []; } if (ctx.refDepthReached) { const needed = schema.minItems ?? 0; return needed > 0 ? Array.from({ length: needed }, () => null) : []; } if (schema.items === false && (schema.minItems ?? 0) > 0) { const prefixItemCount = schema.prefixItems?.length ?? 0; if ((schema.minItems ?? 0) > prefixItemCount) { throw new Error(`missing items for ${schema.minItems} in ${ctx.path}: items is false but minItems (${schema.minItems}) exceeds prefixItems count (${prefixItemCount})`); } } const childCtxPath = ctx.path === "/" ? "/items" : `${ctx.path}/items`; const childCtx = { ...ctx, depth: ctx.depth + 1, path: childCtxPath }; const result = []; const maxRefDepth = ctx.refDepthMax; let minItems = ctx.minItems ?? schema.minItems ?? 0; let maxItems = ctx.maxItems ?? schema.maxItems ?? Math.max(minItems, ctx.maxDefaultItems); if (schema.maxItems !== undefined && maxItems > schema.maxItems) { maxItems = schema.maxItems; } if (schema.minItems !== undefined && minItems < schema.minItems) { minItems = schema.minItems; } if (maxItems < minItems) { if (schema.maxItems !== undefined) { minItems = maxItems; } else { maxItems = minItems; } } if (ctx.alwaysFakeOptionals) { maxItems = Math.max(minItems, maxItems); } const seen = schema.uniqueItems ? new Set : null; const addItem = async (itemSchema, itemCtx) => { let item = await walk(itemSchema, itemCtx); if (item === undefined) { return "omitted"; } if (seen) { const key = JSON.stringify(item); if (seen.has(key)) { for (let attempts = 0;attempts < 50; attempts++) { item = await walk(itemSchema, createItemContext(childCtx, result.length)); if (item === undefined) { continue; } const newKey = JSON.stringify(item); if (!seen.has(newKey)) { seen.add(newKey); result.push(item); return "added"; } } return "failed"; } seen.add(key); } result.push(item); return "added"; }; if (schema.prefixItems) { for (let i = 0;i < schema.prefixItems.length && result.length < maxItems; i++) { const itemCtx = createItemContext(childCtx, i); const status = await addItem(schema.prefixItems[i], itemCtx); if (status === "failed") break; } } let targetLen; if (maxRefDepth !== undefined && ctx.refDepth >= maxRefDepth - 1) { const needed = schema.minItems ?? 0; return needed > 0 ? Array.from({ length: needed }, () => null) : []; } const alwaysFakeOptionals = ctx.alwaysFakeOptionals ?? false; const useFixedProbabilities = ctx.fixedProbabilities ?? false; const optionalsProbability = ctx.optionalsProbability ?? 0.5; if (alwaysFakeOptionals) { targetLen = maxItems; } else if (useFixedProbabilities) { const availableRange = maxItems - Math.max(minItems, result.length); const additionalItems = Math.round(availableRange * optionalsProbability); targetLen = Math.max(minItems, result.length) + additionalItems; } else { targetLen = ctx.random.int(Math.max(minItems, result.length), maxItems); } const hasExplicitItems = schema.items !== undefined; const hasPrefixItems = schema.prefixItems !== undefined; const hasAdditionalItems = schema.additionalItems !== undefined; let additionalItemSchema; if (hasPrefixItems) { if (hasAdditionalItems) { additionalItemSchema = schema.additionalItems; } else if (schema.items !== undefined) { additionalItemSchema = schema.items; } } else { additionalItemSchema = schema.items; } if (additionalItemSchema !== false && additionalItemSchema !== undefined) { const itemSchema = additionalItemSchema ?? {}; let attempts = 0; const maxAttempts = Math.max(targetLen * 20, 50); while (result.length < targetLen) { const status = await addItem(itemSchema, createItemContext(childCtx, result.length)); if (status === "added") { attempts = 0; continue; } attempts++; if (status === "failed" || attempts >= maxAttempts) break; } } if (schema.contains) { const minContains = schema.minContains ?? 1; const maxContains = schema.maxContains ?? Math.max(minContains, 1); const containsCount = ctx.random.int(minContains, maxContains); for (let i = 0;i < containsCount; i++) { let item = await walk(schema.contains, createItemContext(childCtx, result.length)); if (item === undefined) { continue; } if (seen) { const key = JSON.stringify(item); if (seen.has(key)) { let uniqueFound = false; for (let attempts = 0;attempts < 50; attempts++) { item = await walk(schema.contains, createItemContext(childCtx, result.length)); if (item === undefined) { continue; } const newKey = JSON.stringify(item); if (!seen.has(newKey)) { seen.add(newKey); uniqueFound = true; break; } } if (!uniqueFound) continue; } else { seen.add(key); } } if (result.length < maxItems) { const pos = ctx.random.int(0, result.length); result.splice(pos, 0, item); } else if (result.length > 0) { const pos = ctx.random.int(0, result.length - 1); result[pos] = item; } } while (result.length > maxItems) { result.pop(); } } if (schema.containsAll) { const claimedPositions = new Set; for (const containsSchema of schema.containsAll) { let item = await walk(containsSchema, createItemContext(childCtx, result.length)); if (item === undefined) { continue; } if (seen) { const key = JSON.stringify(item); if (seen.has(key)) { let uniqueFound = false; for (let attempts = 0;attempts < 50; attempts++) { item = await walk(containsSchema, createItemContext(childCtx, result.length)); if (item === undefined) { continue; } const newKey = JSON.stringify(item); if (!seen.has(newKey)) { seen.add(newKey); uniqueFound = true; break; } } if (!uniqueFound) continue; } else { seen.add(key); } } if (result.length < maxItems) { const pos = ctx.random.int(0, result.length); result.splice(pos, 0, item); const shifted = new Set; for (const p of claimedPositions) { shifted.add(p >= pos ? p + 1 : p); } claimedPositions.clear(); for (const p of shifted) claimedPositions.add(p); claimedPositions.add(pos); } else if (result.length > 0) { const available = Array.from({ length: result.length }, (_, i) => i).filter((i) => !claimedPositions.has(i)); if (available.length > 0) { const pos = available[ctx.random.int(0, available.length - 1)]; result[pos] = item; claimedPositions.add(pos); } else { result.push(item); claimedPositions.add(result.length - 1); } } } while (result.length > maxItems) { result.pop(); } } if (schema.uniqueItems && result.length > 0) { return fillUniqueItems(result, schema, childCtx, minItems, maxItems, seen); } return result; } async function fillUniqueItems(arr, schema, ctx, minItems, maxItems, seen) { const itemSchema = schema.items ?? {}; let attempts = 0; const maxAttempts = 100; while (arr.length < minItems && attempts < maxAttempts) { attempts++; const item = await walk(itemSchema, createItemContext(ctx, arr.length)); if (item ==