โ Critical Bugs Fixed
We've resolved two major issues affecting array-of-objects validation:
- TypeScript Type Inference: Arrays now correctly infer as
Array<T>instead of tuple types - Runtime Validation: Array elements are now properly validated against their schema
Array-of-Objects Type Inference
FIXEDโ Problem
When defining array-of-objects using the syntax videos: [{ ... }],
TypeScript was incorrectly inferring it as a tuple type requiring exactly one element,
instead of Array<T>.
TypeScript Error:
// Before fix - TypeScript error
const schema = Interface({
videos: [{
id: "string",
title: "string"
}]
});
const data = {
videos: [] // โ Error: Property '0' is missing in type '[]'
};
๐ Root Cause
In src/core/schema/mode/interfaces/typescript/TypeInference.ts,
the InferFieldType type didn't check for readonly [T]
syntax before checking Array<T>. TypeScript treats [{...}]
as a tuple type by default.
// Before - Missing tuple check
export type InferFieldType<T> =
T extends string
? HandleOptional<MapFieldType<T>, IsOptional<T>>
: T extends Array<infer U> // โ Doesn't match tuple types
? Array<InferSchemaType<U>>
: // ... other types
โ Solution
Added check for T extends readonly [infer U] before the Array check.
This ensures tuple syntax is properly detected and converted to array type.
// After - Tuple check added
export type InferFieldType<T> =
T extends string
? HandleOptional<MapFieldType<T>, IsOptional<T>>
: T extends readonly [infer U] // โ
Check tuple first
? Array<InferSchemaType<U>>
: T extends Array<infer U>
? Array<InferSchemaType<U>>
: // ... other types
๐ Result
// After fix - Works correctly
const schema = Interface({
videos: [{
id: "string",
title: "string"
}]
});
type InferredType = InferType<typeof schema>;
// โ
{ videos: Array<{ id: string; title: string }> }
const data1 = { videos: [] }; // โ
Valid
const data2 = { videos: [{ id: "1", title: "Video" }] }; // โ
Valid
Array-of-Objects Runtime Validation
FIXEDโ Problem
Array-of-objects validation was completely broken. Invalid data was passing through without any validation:
โ Missing Fields
Required fields were not validated
โ Wrong Types
Type mismatches were ignored
โ Not Even Array
Objects passed as arrays
// Before fix - All these incorrectly passed โ
const schema = Interface({
videos: [{
id: "string",
title: "string",
duration: "number"
}]
});
// Missing required fields - should FAIL but PASSED
schema.parse({ videos: [{ id: "1" }] }); // โ
// Wrong types - should FAIL but PASSED
schema.parse({ videos: [{ id: 123, title: "Test", duration: "wrong" }] }); // โ
// Not even an array - should FAIL but PASSED
schema.parse({ videos: { id: "1", title: "Test" } }); // โ
๐ Root Cause
The precompiled validator was being used for schemas with array-of-objects, but the precompiler
doesn't handle them. It was calling String(fieldType) on the array [{ ... }],
converting it to "[object Object]", and creating a broken validator that did nothing.
// In SchemaPrecompiler.ts - The bug
private static compileField(fieldName: string, fieldType: any) {
if (typeof fieldType === "object" && !Array.isArray(fieldType)) {
// Handle nested objects
}
// โ BUG: Arrays fall through to here
const fieldTypeStr = String(fieldType); // "[object Object]"
const validator = FieldPrecompilers.parseAndCompile(fieldTypeStr);
// Creates broken validator!
}
โ Solution
Added check in InterfaceSchema.ts to detect array-of-objects fields and skip precompilation,
forcing the use of standard validation which correctly handles arrays.
// In InterfaceSchema.ts - The fix
private createPrecompiledValidator(): void {
const hasConditionalFields = this.compiledFields.some(
(field) => field.isConditional
);
// โ
NEW: Check for array-of-objects fields
const hasArrayOfObjects = this.compiledFields.some(
(field) => Array.isArray(field.originalType)
);
// Skip precompilation if array-of-objects present
if (!hasConditionalFields && !hasArrayOfObjects && /* other checks */) {
this.precompiledValidator = SchemaPrecompiler.precompileSchema(...);
}
// Otherwise use standard validation (which works correctly)
}
๐ Result
// After fix - Validation works correctly โ
const schema = Interface({
videos: [{
id: "string",
title: "string",
duration: "number"
}]
});
// Empty array - PASSES (correct, no minimum constraint)
schema.parse({ videos: [] }); // โ
// Valid data - PASSES
schema.parse({
videos: [{ id: "1", title: "Video", duration: 120 }]
}); // โ
// Missing fields - FAILS (correct)
schema.parse({ videos: [{ id: "1" }] });
// โ Error: Missing required field: title
// Wrong types - FAILS (correct)
schema.parse({ videos: [{ id: 123, title: "Test", duration: "wrong" }] });
// โ Error: Expected String, but received Number
// Not an array - FAILS (correct)
schema.parse({ videos: { id: "1" } });
// โ Error: Expected Array, but received Object
โ Test Results
| Test Case | Before | After | Status |
|---|---|---|---|
| Empty array | PASS | PASS | โ Correct |
| Valid array elements | PASS | PASS | โ Correct |
| Missing required fields | PASS | FAIL | โ Fixed |
| Wrong types | PASS | FAIL | โ Fixed |
| Not an array | PASS | FAIL | โ Fixed |
| TypeScript type inference | Tuple | Array | โ Fixed |
๐ Migration Guide
Good news! These fixes are backward compatible. No changes needed to your existing code.
- Empty arrays remain valid by default (use array length constraints if you need minimums)
- TypeScript types are now more accurate
- Validation is now stricter and catches errors that were previously missed