ObjectValidator

See source code
Table 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

NameDescription

config

{
  readonly [K in keyof Shape]: Validatable<Shape[K]>;
};

shouldAllowUnknownProperties

boolean;

Properties

config

readonly
readonly config: {
  readonly [K in keyof Shape]: Validatable<Shape[K]>;
};

validateUsingKnownGoodVersionFn

readonlyoptional
readonly validateUsingKnownGoodVersionFn?:
  | undefined
  | ValidatorUsingKnownGoodVersionFn<T, T>;

validationFn

readonly
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

NameDescription

name

string;

Name for the check (used in error messages)

checkFn

(value: T) => void;

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

NameDescription

extension

{
  readonly [K in keyof Extension]: Validatable<
    Extension[K]
  >;
};

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

NameDescription

value

unknown;

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

NameDescription

otherValidationFn

(value: T) => U;

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

NameDescription

value

unknown;

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

NameDescription

knownGoodValue

T;

A previously validated value

newValue

unknown;

The new value to validate

Returns

T;

The validated value, potentially reusing the known good value


Prev
DictValidator
Next
UnionValidator