Add support for nested objects and arrays in Zod schema parser (#5098)

feat(secureZodParser): Add support for nested objects and arrays of primitives
This commit is contained in:
Ong Chung Yau
2025-08-18 18:35:24 +08:00
committed by GitHub
parent e5381f5090
commit 9cac8d7a00
+107
View File
@@ -135,6 +135,28 @@ export class SecureZodSchemaParser {
}
private static parseZodType(typeStr: string): any {
// Check if this is a nested object (not in an array)
if (typeStr.startsWith('z.object(') && !typeStr.startsWith('z.array(')) {
// Extract object content
const objectMatch = typeStr.match(/z\.object\(\s*\{([\s\S]*)\}\s*\)/)
if (!objectMatch) {
throw new Error('Invalid object syntax')
}
const objectContent = objectMatch[1]
const objectProperties = this.parseObjectProperties(objectContent)
return {
isNestedObject: true,
objectSchema: objectProperties
}
}
// Check if this is any kind of array
if (typeStr.startsWith('z.array(')) {
return this.parseArray(typeStr)
}
const type: { base: string; modifiers: any[]; baseArgs?: any[] } = { base: '', modifiers: [] }
// Handle chained methods like z.string().max(500).optional()
@@ -181,6 +203,74 @@ export class SecureZodSchemaParser {
return type
}
private static parseArray(typeStr: string): any {
// Extract the content inside array()
const arrayContentMatch = typeStr.match(/z\.array\(\s*([\s\S]*)\s*\)$/)
if (!arrayContentMatch) {
throw new Error('Invalid array syntax')
}
const arrayContent = arrayContentMatch[1].trim()
// Parse the object inside the array
if (arrayContent.startsWith('z.object(')) {
// Extract object content
const objectMatch = arrayContent.match(/z\.object\(\s*\{([\s\S]*)\}\s*\)/)
if (!objectMatch) {
throw new Error('Invalid object syntax inside array')
}
const objectContent = objectMatch[1]
const objectProperties = this.parseObjectProperties(objectContent)
// Validate each property in the nested object
for (const propValue of Object.values(objectProperties)) {
this.validateTypeInfo(propValue)
}
return {
isArrayOfObjects: true,
objectSchema: objectProperties
}
}
// Handle simple arrays (e.g., z.array(z.string()))
const innerType = this.parseZodType(arrayContent)
return {
isSimpleArray: true,
innerType: innerType
}
}
private static validateTypeInfo(typeInfo: any): void {
// If it's a nested object or array of objects, validate each property
if (typeInfo.isNestedObject || typeInfo.isArrayOfObjects) {
for (const propValue of Object.values(typeInfo.objectSchema)) {
this.validateTypeInfo(propValue)
}
return
}
// If it's a simple array, validate the inner type
if (typeInfo.isSimpleArray) {
this.validateTypeInfo(typeInfo.innerType)
return
}
// Validate base type
if (!this.ALLOWED_TYPES.includes(typeInfo.base)) {
throw new Error(`Unsupported type: ${typeInfo.base}`)
}
// Validate modifiers
for (const modifier of typeInfo.modifiers || []) {
if (!this.ALLOWED_TYPES.includes(modifier.name)) {
throw new Error(`Unsupported modifier: ${modifier.name}`)
}
}
}
private static parseArguments(argsStr: string): any[] {
// Remove outer parentheses
const inner = argsStr.slice(1, -1).trim()
@@ -250,6 +340,23 @@ export class SecureZodSchemaParser {
}
private static buildZodType(typeInfo: any): z.ZodTypeAny {
// Special case for nested objects
if (typeInfo.isNestedObject) {
return this.buildZodSchema(typeInfo.objectSchema)
}
// Special case for array of objects
if (typeInfo.isArrayOfObjects) {
const objectSchema = this.buildZodSchema(typeInfo.objectSchema)
return z.array(objectSchema)
}
// Special case for simple arrays
if (typeInfo.isSimpleArray) {
const innerZodType = this.buildZodType(typeInfo.innerType)
return z.array(innerZodType)
}
let zodType: z.ZodTypeAny
// Build base type