ValidationErrors Runtime
Validation modes: Validation modes reference
Declaration. validate() returns ValidationErrors (not string[]). See validate() for the method reference. This page covers the ValidationErrors collection shape and usage patterns.
The ValidationErrors collection is also carried on InstantiationError.errors and CoercionError.errors, so the same patterns apply when you catch those exceptions.
Use this when you need programmatic access to the structured error list - paths, keywords, params - without wanting an exception. This is the right collection for API validation where you collect errors, then decide what to do with them (return a 422, log, display in a form). The collection is iterable with for...of.
Don't use this when you only need a boolean (use is). Don't use it when you want the coerced typed value on success (use instantiate).
Public surface
| Member | Type | Purpose |
|---|---|---|
items | readonly ValidationErrorType[] | Raw error list with JSON Pointer paths |
length | number | Number of errors |
ok | boolean | true when length === 0 |
aggregate() | AggregateViewType | { count, paths, keywords } rollup for logs and metrics |
report(overrides?) | ProblemDetailsType | RFC 7807 Problem Details payload |
[Symbol.iterator]() | Iterator<ValidationErrorType> | Enables for...of |
Each ValidationErrorType carries:
type ValidationErrorType = {
path: string; // JSON Pointer path
keyword: string; // e.g. 'required', 'type', 'jt:invariant'
message: string; // human-readable
params: Record<string, unknown> // keyword-specific params
};Examples
Example 1: Check validity, iterate errors
/**
* ValidationErrors — Example 1: Check validity, iterate errors
* Demonstrates: .ok, .length, for...of iteration, .path, .keyword, .message, .params
*
* An Order with a negative total and an empty items array violates
* `exclusiveMinimum` and `minItems` — surfaces at least two structured errors.
*/
import {
aboxFixtures, bookstoreEntities, OrderSchema
} from '../bookstore/index.js';
const errs = bookstoreEntities.validate(OrderSchema.$id, {
'customerId': aboxFixtures.customer.customerId,
'orderId': aboxFixtures.order.orderId,
// minItems: 1 violated
'orderLines': [],
'orderTotal': {
// exclusiveMinimum: 0 violated
'amount': -5,
'currency': 'EUR'
},
'placedAt': '2026-01-15T10:30:00Z',
'shippingAddress': aboxFixtures.order.shippingAddress
});
console.assert(!errs.ok);
console.assert(errs.length >= 2);
console.log('ok:', errs.ok, ', error count:', errs.length);
for (const err of errs) {
console.assert(typeof err.path === 'string');
console.assert(typeof err.keyword === 'string');
console.assert(typeof err.message === 'string');
console.assert(typeof err.params === 'object');
console.log(` path="${err.path}" keyword="${err.keyword}" message="${err.message}"`);
}
Example 2: Valid data returns empty collection
/**
* ValidationErrors — Example 2: Valid data returns empty collection
* Demonstrates: .ok === true, .length === 0 on valid input
*
* Michael Ende's "Die unendliche Geschichte" — Thienemann Verlag, 1979.
* All required fields present and valid; collection is empty.
*/
import {
aboxFixtures, BookSchema, bookstoreEntities
} from '../bookstore/index.js';
const errs = bookstoreEntities.validate(BookSchema.$id, {
'authors': ['Michael Ende'],
'isbn': aboxFixtures.rareBook.isbn,
'price': aboxFixtures.rareBook.price,
'printStatus': 'outOfPrint',
'title': aboxFixtures.rareBook.title
});
console.assert(errs.ok);
console.assert(errs.length === 0);
console.log('ok:', errs.ok, ', errors:', errs.length);
Example 3: Combine with the structured views
See Error views for full documentation of each view.
/**
* ValidationErrors — Example 3: Structured views on a failed review
* Demonstrates: .items map, .aggregate(), .report()
*
* A review with a rating above the maximum and too-short body
* triggers multiple validation errors.
*/
import {
aboxFixtures, bookstoreEntities, ReviewSchema
} from '../bookstore/index.js';
const errs = bookstoreEntities.validate(ReviewSchema.$id, {
// minLength: 10 violated
'body': 'hi',
'bookIsbn': aboxFixtures.rareBook.isbn,
'customerId': aboxFixtures.customer.customerId,
'postedAt': '2026-04-20T09:15:00Z',
// maximum: 5 violated
'rating': 6,
'reviewId': aboxFixtures.review.reviewId
});
console.assert(!errs.ok);
// string[] — one message per error
const messages = errs.items.map((err) => {
return `${err.path}: ${err.message}`;
});
console.assert(Array.isArray(messages));
console.assert(messages.length > 0);
console.log('field errors:', messages);
// Aggregate rollup for logs and metrics
const agg = errs.aggregate();
console.assert(typeof agg.count === 'number' && agg.count > 0);
console.assert(Array.isArray(agg.paths));
console.assert(Array.isArray(agg.keywords));
console.log('aggregate: count =', agg.count, ', paths =', agg.paths, ', keywords =', agg.keywords);
// RFC 7807 Problem Details payload
const problem = errs.report();
console.assert(typeof problem === 'object');
console.assert('status' in problem && (problem as { 'status': number }).status === 422);
console.log('report status:', (problem as { 'status': number }).status);
Bad examples - what NOT to do
Anti-pattern 1: Calling validate() and then instantiate() separately
/**
* ValidationErrors — Anti-pattern 1: validate() then instantiate() separately
* Demonstrates: the bad pattern (double validation) vs the correct catch pattern
*
* Bastian Balthazar Bux — valid customer used for the correct path.
*/
import { InstantiationError } from '../../../src/index.js';
import {
aboxFixtures, bookstoreEntities, CustomerSchema
} from '../bookstore/index.js';
// Anti-pattern: validate then instantiate — validation runs twice
// Don't do this
const errs = bookstoreEntities.validate(CustomerSchema, aboxFixtures.customer);
if (errs.ok) {
// validates again — redundant
const _customer = bookstoreEntities.instantiate(CustomerSchema, aboxFixtures.customer);
void _customer;
}
// Correct approach: instantiate directly; catch InstantiationError if invalid
try {
const customer = bookstoreEntities.instantiate(CustomerSchema, aboxFixtures.customer);
console.assert(customer.name === aboxFixtures.customer.name);
console.log('instantiate succeeded:', customer.name);
} catch (error) {
if (error instanceof InstantiationError) {
// same ValidationErrors on InstantiationError
const problem = error.errors.report();
console.assert(typeof problem === 'object');
} else {
throw error;
}
}
Anti-pattern 2: Re-implementing a built-in view
/**
* ValidationErrors — Anti-pattern 2: Re-implementing a built-in view
* Demonstrates: manual grouping (bad) vs .aggregate() (correct)
*
* A review with invalid rating and short body produces multiple field errors.
* Rolling your own accumulator loop duplicates what .aggregate() provides.
*/
import {
aboxFixtures, bookstoreEntities, ReviewSchema
} from '../bookstore/index.js';
const errs = bookstoreEntities.validate(ReviewSchema.$id, {
'body': 'hi',
'bookIsbn': aboxFixtures.rareBook.isbn,
'customerId': aboxFixtures.customer.customerId,
'postedAt': '2026-04-20T09:15:00Z',
'rating': 6,
'reviewId': aboxFixtures.review.reviewId
});
// Anti-pattern: rolling your own path-to-messages accumulator
// Don't do this
const manualGrouped: Record<string, string[]> = {};
for (const item of errs.items) {
const key = item.path.length > 0 ? item.path : '_root';
(manualGrouped[key] ??= []).push(item.message);
}
// Correct approach: use .aggregate() or .items directly
const agg = errs.aggregate();
console.assert(agg.count === errs.length);
console.assert(agg.paths.length <= errs.length);
// The aggregate paths cover the same fields as manual grouping.
// agg.paths strips the leading slash; manualGrouped keys retain it from item.path.
console.assert(
agg.paths.every((path) => {
const withSlash = `/${path}`;
return (
Object.keys(manualGrouped).includes(path)
|| Object.keys(manualGrouped).includes(withSlash)
|| path === '_root'
);
}),
'aggregate paths should correspond to the manually grouped paths'
);
console.log('aggregate count:', agg.count, ', paths:', agg.paths);
Comparison
const errs = bookstoreEntities.validate(OrderSchema.$id, data);
// ValidationErrors - .ok, .length, iterable, .items, .aggregate(), .report()const result = OrderSchema.safeParse(data);
if (!result.success) {
result.error.issues; // ZodIssue[] - path (array), code, message per issue
result.error.flatten(); // { fieldErrors, formErrors } - Zod-native flatten
}import * as v from 'valibot';
const result = v.safeParse(OrderSchema, data);
if (!result.success) {
result.issues; // Issue[] - .message, .path, .expected, .received per issue
// Limitation: no built-in aggregate() or report() views; project manually.
v.flatten(result.issues); // { root, nested } summary
}import { isLeft } from 'fp-ts/Either';
import { PathReporter } from 'io-ts/PathReporter';
const result = OrderCodec.decode(data);
if (isLeft(result)) {
result.left; // ValidationError[] - context + value per node
PathReporter.report(result); // string[] - flattened messages
// Limitation: no aggregate() or report() RFC 7807 views; project manually
// from the ValidationError context array.
}import { Value } from '@sinclair/typebox/value';
const errors = [...Value.Errors(OrderSchema, data)];
// ValueError[] - path, message, schema, value per error
// No built-in viewsajv.validate(orderSchema, data);
const errors = ajv.errors ?? [];
// ErrorObject[] - instancePath, keyword, message, params per error
// No built-in viewstry:
Order(**data)
except ValidationError as e:
e.errors() # list of dicts: loc, msg, type
e.error_count() # int
e.json() # JSON string of errors
# No aggregate() or report() equivalent built in// Limitation: feature not directly supported in Yup. See /comparisons for the matrix.// Limitation: feature not directly supported in Joi. See /comparisons for the matrix.// Limitation: feature not directly supported in Effect Schema. See /comparisons for the matrix.// Limitation: feature not directly supported in ArkType. See /comparisons for the matrix.// Limitation: feature not directly supported in Runtypes. See /comparisons for the matrix.Related
JsonTology.validate- method that returnsValidationErrorsJsonTology.is- boolean type guardJsonTology.instantiate- throwsInstantiationErrorwhich carries the sameValidationErrorson.errors- Error views -
aggregate,reportin full detail
See also
- Invariants - cross-field rules that produce
ValidationErrorTypeitems withkeyword: 'jt:invariant' - Bookstore domain - schema definitions used in examples