JavaScript Proxy Set Trap for Data Validation

The JavaScript Proxy object enables developers to intercept and customize fundamental operations on objects, such as property access, assignment, and function invocation. Among its handler methods, the set trap is specifically designed to intercept property write operations. By executing custom logic before a property value is modified or added, the set trap serves as a powerful mechanism to enforce strict data validation rules, maintain schema integrity, and prevent invalid state changes across an application.

Understanding the Proxy set Trap

A Proxy wraps a target object and delegates operations through a handler object containing “traps.” The set trap is triggered whenever a property assignment occurs on the proxy instance (e.g., proxy.property = value or proxy[key] = value).

The set method accepts four parameters:

  1. target: The original underlying object being proxied.
  2. property: The name (string or Symbol) of the property to set.
  3. value: The new value being assigned to the property.
  4. receiver: The object to which the assignment was originally directed (usually the proxy itself).

To complete the operation correctly, the set trap must return a boolean: true if the assignment was successful, or false if the assignment failed. In strict mode ('use strict'), returning false will automatically throw a TypeError.

const handler = {
  set(target, property, value, receiver) {
    // Custom logic here
    target[property] = value;
    return true;
  }
};

How the set Trap Enforces Validation Rules

Validation with a set trap works by placing guard clauses and type checks before mutating the target object. If the incoming value fails any validation condition, the handler can throw a custom Error or return false to abort the write operation.

Step-by-Step Implementation

  1. Intercept the Property: Determine which property is being modified.
  2. Evaluate the Constraints: Check the value’s type, range, format, or business rules.
  3. Handle Violations: Throw descriptive errors to notify the caller of the failure.
  4. Persist Valid Data: Assign the validated value to the target using direct assignment or Reflect.set(target, property, value, receiver) and return true.

Code Example: Validating a User Profile

'use strict';

const userSchemaValidator = {
  set(target, property, value) {
    if (property === 'age') {
      if (typeof value !== 'number' || !Number.isInteger(value)) {
        throw new TypeError('Age must be an integer.');
      }
      if (value < 0 || value > 120) {
        throw new RangeError('Age must be between 0 and 120.');
      }
    }

    if (property === 'email') {
      const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
      if (typeof value !== 'string' || !emailRegex.test(value)) {
        throw new Error('Invalid email format.');
      }
    }

    if (property === 'username') {
      if (typeof value !== 'string' || value.trim().length < 3) {
        throw new Error('Username must be at least 3 characters long.');
      }
    }

    // Apply the valid value
    target[property] = value;
    return true;
  }
};

const user = new Proxy({}, userSchemaValidator);

// Valid assignments
user.username = 'alex_dev';
user.age = 28;
user.email = 'alex@example.com';

// Invalid assignment: Throws RangeError
// user.age = 150;

// Invalid assignment: Throws TypeError
// user.age = 'twenty-eight';

// Invalid assignment: Throws Error
// user.email = 'invalid-email-string';

Key Benefits of Using set Traps for Validation