ObjectValidator
See source codeTable of contents
Extends Validator<Shape>
.
Validator for objects with a defined shape. Each property is validated using its corresponding validator from the config object. Can be configured to allow or reject unknown properties.
class ObjectValidator<Shape extends object> extends Validator<Shape> {}
Example
const userValidator = new ObjectValidator({
name: T.string,
age: T.number,
email: T.string.optional(),
})
const user = userValidator.validate({
name: 'Alice',
age: 25,
email: 'alice@example.com',
})
Constructor
Creates a new ObjectValidator.
config - Object mapping property names to their validators shouldAllowUnknownProperties - Whether to allow properties not defined in config
Parameters
Name | Description |
---|---|
|
|
|
|
Properties
config
readonly config: {
readonly [K in keyof Shape]: Validatable<Shape[K]>
}
validateUsingKnownGoodVersionFn
readonly validateUsingKnownGoodVersionFn?:
| undefined
| ValidatorUsingKnownGoodVersionFn<T>
validationFn
readonly validationFn: ValidatorFn<T>
Methods
allowUnknownProperties( )
Returns a new validator that allows unknown properties in the validated object.
allowUnknownProperties(): ObjectValidator<Shape>
Example
const flexibleUser = T.object({ name: T.string }).allowUnknownProperties()
flexibleUser.validate({ name: 'Alice', extra: 'allowed' }) // Valid
check( )
Adds an additional validation check without changing the resulting value type. Can be called with just a check function, or with a name for better error messages.
check(name: string, checkFn: (value: T) => void): Validator<T>
Example
import { T, ValidationError } from '@tldraw/validate'
// Basic check without name
const evenNumber = T.number.check((value) => {
if (value % 2 !== 0) {
throw new ValidationError('Expected even number')
}
})
// Named checks for better error messages in complex validators
const shapePositionValidator = T.object({
x: T.number.check('finite', (value) => {
if (!Number.isFinite(value)) {
throw new ValidationError('Position must be finite')
}
}),
y: T.number.check('within-bounds', (value) => {
if (value < -10000 || value > 10000) {
throw new ValidationError(
'Position must be within bounds (-10000 to 10000)'
)
}
}),
})
// Error will be: "At x (check finite): Position must be finite"
Parameters
Name | Description |
---|---|
|
Name for the check (used in error messages) |
|
Function that validates the value (should throw on invalid input) |
Returns
Validator<T>
A new validator with the additional check
extend( )
Creates a new ObjectValidator by extending this validator with additional properties.
extend<Extension extends Record<string, unknown>>(extension: {
readonly [K in keyof Extension]: Validatable<Extension[K]>
}): ObjectValidator<Shape & Extension>
Example
const baseUser = T.object({ name: T.string, age: T.number })
const adminUser = baseUser.extend({
permissions: T.arrayOf(T.string),
isAdmin: T.boolean,
})
// adminUser validates: { name: string; age: number; permissions: string[]; isAdmin: boolean }
Parameters
Name | Description |
---|---|
|
Object mapping new property names to their validators |
Returns
ObjectValidator<Shape & Extension>
A new ObjectValidator that validates both original and extended properties
isValid( )
Type guard that checks if a value is valid without throwing an error.
isValid(value: unknown): value is T
Example
import { T } from '@tldraw/validate'
function processUserInput(input: unknown) {
if (T.string.isValid(input)) {
// input is now typed as string within this block
return input.toUpperCase()
}
if (T.number.isValid(input)) {
// input is now typed as number within this block
return input.toFixed(2)
}
throw new Error('Expected string or number')
}
Parameters
Name | Description |
---|---|
|
The value to check |
Returns
value is T
True if the value is valid, false otherwise
nullable( )
Returns a new validator that also accepts null values.
nullable(): Validator<null | T>
Example
import { T } from '@tldraw/validate'
const assetValidator = T.object({
id: T.string,
name: T.string,
src: T.srcUrl.nullable(), // Can be null if not loaded yet
mimeType: T.string.nullable(),
})
const asset = assetValidator.validate({
id: 'image-123',
name: 'photo.jpg',
src: null, // Valid - asset not loaded yet
mimeType: 'image/jpeg',
})
optional( )
Returns a new validator that also accepts undefined values.
optional(): Validator<T | undefined>
Example
import { T } from '@tldraw/validate'
const shapeConfigValidator = T.object({
type: T.literal('rectangle'),
x: T.number,
y: T.number,
label: T.string.optional(), // Optional property
metadata: T.object({ created: T.string }).optional(),
})
// Both of these are valid:
const shape1 = shapeConfigValidator.validate({ type: 'rectangle', x: 0, y: 0 })
const shape2 = shapeConfigValidator.validate({
type: 'rectangle',
x: 0,
y: 0,
label: 'My Shape',
})
refine( )
Creates a new validator by refining this validator with additional logic that can transform the validated value to a new type.
refine<U>(otherValidationFn: (value: T) => U): Validator<U>
Example
import { T, ValidationError } from '@tldraw/validate'
// Transform string to ensure it starts with a prefix
const prefixedIdValidator = T.string.refine((id) => {
return id.startsWith('shape:') ? id : `shape:${id}`
})
const id1 = prefixedIdValidator.validate('rectangle-123') // Returns "shape:rectangle-123"
const id2 = prefixedIdValidator.validate('shape:circle-456') // Returns "shape:circle-456"
// Parse and validate JSON strings
const jsonValidator = T.string.refine((str) => {
try {
return JSON.parse(str)
} catch {
throw new ValidationError('Invalid JSON string')
}
})
Parameters
Name | Description |
---|---|
|
Function that transforms/validates the value to type U |
Returns
Validator<U>
A new validator that validates to type U
validate( )
Validates an unknown value and returns it with the correct type. The returned value is guaranteed to be referentially equal to the passed value.
validate(value: unknown): T
Example
import { T } from '@tldraw/validate'
const name = T.string.validate('Alice') // Returns "Alice" as string
const title = T.string.validate('') // Returns "" (empty strings are valid)
// These will throw ValidationError:
T.string.validate(123) // Expected string, got a number
T.string.validate(null) // Expected string, got null
T.string.validate(undefined) // Expected string, got undefined
Parameters
Name | Description |
---|---|
|
The unknown value to validate |
Returns
T
The validated value with type T
validateUsingKnownGoodVersion( )
Performance-optimized validation using a previously validated value. If the new value is referentially equal to the known good value, returns the known good value immediately.
validateUsingKnownGoodVersion(knownGoodValue: T, newValue: unknown): T
Example
import { T } from '@tldraw/validate'
const userValidator = T.object({
name: T.string,
settings: T.object({ theme: T.literalEnum('light', 'dark') }),
})
const user = userValidator.validate({
name: 'Alice',
settings: { theme: 'light' },
})
// Later, with partially changed data:
const newData = { name: 'Alice', settings: { theme: 'dark' } }
const updated = userValidator.validateUsingKnownGoodVersion(user, newData)
// Only validates the changed 'theme' field for better performance
Parameters
Name | Description |
---|---|
|
A previously validated value |
|
The new value to validate |
Returns
T
The validated value, potentially reusing the known good value