Skip to main content

Advanced Object Manipulation in JavaScript

Created: May 8, 2026 Larry Qu 10 min read

Advanced object manipulation enables fine-grained control over object behavior. This article covers property descriptors, freezing, sealing, cloning, and transformation patterns. See Javascript Guide for more context. See Javascript Guide for more context.

Introduction

Advanced object manipulation allows:

  • Fine-grained property control
  • Object immutability
  • Deep cloning
  • Property transformation
  • Object composition

Understanding these techniques helps you:

  • Create immutable data structures
  • Prevent accidental modifications
  • Implement complex transformations
  • Build robust APIs
  • Optimize performance

Property Descriptors

Understanding Descriptors

// ✅ Good: Inspect property descriptors
const obj = { name: 'John' };

const descriptor = Object.getOwnPropertyDescriptor(obj, 'name');
console.log(descriptor);
// {
//   value: 'John',
//   writable: true,
//   enumerable: true,
//   configurable: true
// }

// Descriptor properties:
// - value: the property value
// - writable: can the value be changed?
// - enumerable: shows up in for...in loops?
// - configurable: can the descriptor be changed?

Creating Properties with Descriptors

// ✅ Good: Define properties with specific descriptors
const obj = {};

Object.defineProperty(obj, 'id', {
  value: 123,
  writable: false,
  enumerable: true,
  configurable: false
});

console.log(obj.id); // 123
obj.id = 456; // Silently fails (or throws in strict mode)
console.log(obj.id); // 123 (unchanged)

// ✅ Good: Define multiple properties
Object.defineProperties(obj, {
  name: {
    value: 'John',
    writable: true,
    enumerable: true,
    configurable: true
  },
  email: {
    value: '[email protected]',
    writable: false,
    enumerable: true,
    configurable: false
  }
});

console.log(obj.name); // 'John'
console.log(obj.email); // '[email protected]'

Accessor Descriptors

// ✅ Good: Use accessor descriptors for computed properties
const obj = {};

Object.defineProperty(obj, 'fullName', {
  get() {
    return `${this.firstName} ${this.lastName}`;
  },
  set(value) {
    [this.firstName, this.lastName] = value.split(' ');
  },
  enumerable: true,
  configurable: true
});

obj.firstName = 'John';
obj.lastName = 'Doe';
console.log(obj.fullName); // 'John Doe'

obj.fullName = 'Jane Smith';
console.log(obj.firstName); // 'Jane'
console.log(obj.lastName); // 'Smith'

Object Freezing and Sealing

Object.freeze()

// ✅ Good: Freeze objects to prevent modifications
const user = {
  name: 'John',
  age: 30
};

Object.freeze(user);

user.name = 'Jane'; // Silently fails
user.email = '[email protected]'; // Silently fails
delete user.age; // Silently fails

console.log(user); // { name: 'John', age: 30 }

// Check if frozen
console.log(Object.isFrozen(user)); // true

Object.seal()

// ✅ Good: Seal objects to prevent property addition/deletion
const config = {
  host: 'localhost',
  port: 3000
};

Object.seal(config);

config.host = '127.0.0.1'; // OK - can modify
config.timeout = 5000; // Fails - can't add new properties
delete config.port; // Fails - can't delete properties

console.log(config); // { host: '127.0.0.1', port: 3000 }

// Check if sealed
console.log(Object.isSealed(config)); // true

Object.preventExtensions()

// ✅ Good: Prevent adding new properties
const obj = { a: 1 };

Object.preventExtensions(obj);

obj.a = 2; // OK - can modify
obj.b = 3; // Fails - can't add new properties

console.log(obj); // { a: 2 }

// Check if extensible
console.log(Object.isExtensible(obj)); // false

Deep Freezing

// ✅ Good: Recursively freeze nested objects
function deepFreeze(obj) {
  Object.freeze(obj);

  Object.getOwnPropertyNames(obj).forEach(prop => {
    if (obj[prop] !== null &&
        (typeof obj[prop] === 'object' || typeof obj[prop] === 'function') &&
        !Object.isFrozen(obj[prop])) {
      deepFreeze(obj[prop]);
    }
  });

  return obj;
}

const config = {
  database: {
    host: 'localhost',
    port: 3000
  },
  cache: {
    ttl: 3600
  }
};

deepFreeze(config);

config.database.host = 'remote'; // Fails
config.cache.ttl = 7200; // Fails

console.log(config);
// { database: { host: 'localhost', port: 3000 }, cache: { ttl: 3600 } }

Object Cloning

Shallow Cloning

// ✅ Good: Shallow clone using Object.assign
const original = {
  name: 'John',
  hobbies: ['reading', 'coding']
};

const clone1 = Object.assign({}, original);
const clone2 = { ...original };

console.log(clone1 === original); // false
console.log(clone1.hobbies === original.hobbies); // true (same reference)

// Modifying nested objects affects both
clone1.hobbies.push('gaming');
console.log(original.hobbies); // ['reading', 'coding', 'gaming']

Deep Cloning

// ✅ Good: Deep clone with recursion
function deepClone(obj, seen = new WeakSet()) {
  // Handle primitives
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }

  // Handle circular references
  if (seen.has(obj)) {
    return obj;
  }

  seen.add(obj);

  // Handle arrays
  if (Array.isArray(obj)) {
    return obj.map(item => deepClone(item, seen));
  }

  // Handle dates
  if (obj instanceof Date) {
    return new Date(obj.getTime());
  }

  // Handle maps
  if (obj instanceof Map) {
    const cloned = new Map();
    obj.forEach((value, key) => {
      cloned.set(deepClone(key, seen), deepClone(value, seen));
    });
    return cloned;
  }

  // Handle sets
  if (obj instanceof Set) {
    const cloned = new Set();
    obj.forEach(value => {
      cloned.add(deepClone(value, seen));
    });
    return cloned;
  }

  // Handle objects
  const cloned = {};
  for (const key of Object.keys(obj)) {
    cloned[key] = deepClone(obj[key], seen);
  }

  return cloned;
}

// Usage
const original = {
  name: 'John',
  hobbies: ['reading', 'coding'],
  metadata: {
    created: new Date(),
    tags: new Set(['js', 'web'])
  }
};

const cloned = deepClone(original);
console.log(cloned === original); // false
console.log(cloned.hobbies === original.hobbies); // false
console.log(cloned.metadata === original.metadata); // false

Structured Clone

// ✅ Good: Use structuredClone for modern browsers
const original = {
  name: 'John',
  hobbies: ['reading', 'coding'],
  date: new Date()
};

const cloned = structuredClone(original);
console.log(cloned === original); // false
console.log(cloned.hobbies === original.hobbies); // false
console.log(cloned.date === original.date); // false

Object Transformation

Mapping Objects

// ✅ Good: Transform object properties
function mapObject(obj, fn) {
  const result = {};
  for (const [key, value] of Object.entries(obj)) {
    result[key] = fn(value, key);
  }
  return result;
}

const numbers = { a: 1, b: 2, c: 3 };
const doubled = mapObject(numbers, (value) => value * 2);
console.log(doubled); // { a: 2, b: 4, c: 6 }

// ✅ Good: Transform keys and values
function transformObject(obj, keyFn, valueFn) {
  const result = {};
  for (const [key, value] of Object.entries(obj)) {
    result[keyFn(key)] = valueFn(value);
  }
  return result;
}

const transformed = transformObject(
  { firstName: 'John', lastName: 'Doe' },
  (key) => key.toUpperCase(),
  (value) => value.toLowerCase()
);
console.log(transformed);
// { FIRSTNAME: 'john', LASTNAME: 'doe' }

Filtering Objects

// ✅ Good: Filter object properties
function filterObject(obj, predicate) {
  const result = {};
  for (const [key, value] of Object.entries(obj)) {
    if (predicate(value, key)) {
      result[key] = value;
    }
  }
  return result;
}

const user = {
  name: 'John',
  age: 30,
  email: '[email protected]',
  password: 'secret'
};

const publicData = filterObject(user, (value, key) => key !== 'password');
console.log(publicData);
// { name: 'John', age: 30, email: '[email protected]' }

Merging Objects

// ✅ Good: Deep merge objects
function deepMerge(target, source) {
  const result = { ...target };

  for (const [key, value] of Object.entries(source)) {
    if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
      result[key] = deepMerge(result[key] || {}, value);
    } else {
      result[key] = value;
    }
  }

  return result;
}

const defaults = {
  database: {
    host: 'localhost',
    port: 3000,
    timeout: 5000
  },
  cache: {
    enabled: true
  }
};

const config = {
  database: {
    host: 'remote.server.com',
    port: 5432
  }
};

const merged = deepMerge(defaults, config);
console.log(merged);
// {
//   database: {
//     host: 'remote.server.com',
//     port: 5432,
//     timeout: 5000
//   },
//   cache: { enabled: true }
// }

Advanced Patterns

Immutable Updates

// ✅ Good: Create immutable updates
function updateImmutable(obj, path, value) {
  const keys = path.split('.');
  const lastKey = keys.pop();

  let current = { ...obj };
  let target = current;

  for (const key of keys) {
    target[key] = { ...target[key] };
    target = target[key];
  }

  target[lastKey] = value;
  return current;
}

const user = {
  name: 'John',
  address: {
    city: 'New York',
    zip: '10001'
  }
};

const updated = updateImmutable(user, 'address.city', 'Boston');
console.log(updated);
// { name: 'John', address: { city: 'Boston', zip: '10001' } }
console.log(user.address.city); // 'New York' (original unchanged)

Proxy-based Immutability

// ✅ Good: Enforce immutability with Proxy
function makeImmutable(obj) {
  return new Proxy(obj, {
    set(target, property, value) {
      throw new Error(`Cannot set property ${String(property)}`);
    },
    deleteProperty(target, property) {
      throw new Error(`Cannot delete property ${String(property)}`);
    }
  });
}

const config = makeImmutable({
  host: 'localhost',
  port: 3000
});

console.log(config.host); // 'localhost'
config.host = 'remote'; // Error: Cannot set property host

Object Composition

// ✅ Good: Compose objects from multiple sources
function compose(...objects) {
  return new Proxy(Object.assign({}, ...objects), {
    get(target, property) {
      const value = target[property];
      return typeof value === 'function' ? value.bind(target) : value;
    }
  });
}

const canEat = {
  eat() {
    return `${this.name} is eating`;
  }
};

const canWalk = {
  walk() {
    return `${this.name} is walking`;
  }
};

const person = compose(
  { name: 'John' },
  canEat,
  canWalk
);

console.log(person.eat()); // 'John is eating'
console.log(person.walk()); // 'John is walking'

Practical Examples

Configuration Manager

// ✅ Good: Immutable configuration manager
class ConfigManager {
  constructor(defaults = {}) {
    this.config = Object.freeze(deepClone(defaults));
  }

  get(path) {
    const keys = path.split('.');
    let value = this.config;

    for (const key of keys) {
      value = value?.[key];
    }

    return value;
  }

  merge(updates) {
    const merged = deepMerge(this.config, updates);
    return new ConfigManager(merged);
  }

  toJSON() {
    return deepClone(this.config);
  }
}

function deepClone(obj) {
  if (obj === null || typeof obj !== 'object') return obj;
  if (Array.isArray(obj)) return obj.map(deepClone);
  const cloned = {};
  for (const key in obj) {
    cloned[key] = deepClone(obj[key]);
  }
  return cloned;
}

function deepMerge(target, source) {
  const result = { ...target };
  for (const [key, value] of Object.entries(source)) {
    if (value !== null && typeof value === 'object') {
      result[key] = deepMerge(result[key] || {}, value);
    } else {
      result[key] = value;
    }
  }
  return result;
}

// Usage
const config = new ConfigManager({
  database: { host: 'localhost', port: 3000 },
  cache: { enabled: true }
});

console.log(config.get('database.host')); // 'localhost'

const newConfig = config.merge({
  database: { host: 'remote.server.com' }
});

console.log(newConfig.get('database.host')); // 'remote.server.com'
console.log(config.get('database.host')); // 'localhost' (original unchanged)

Data Validator

// ✅ Good: Validate and transform objects
class DataValidator {
  constructor(schema) {
    this.schema = schema;
  }

  validate(data) {
    const errors = [];

    for (const [key, rules] of Object.entries(this.schema)) {
      if (!(key in data)) {
        errors.push(`Missing required field: ${key}`);
        continue;
      }

      const value = data[key];

      if (rules.type && typeof value !== rules.type) {
        errors.push(
          `Field ${key}: expected ${rules.type}, got ${typeof value}`
        );
      }

      if (rules.validate && !rules.validate(value)) {
        errors.push(`Field ${key}: ${rules.message}`);
      }
    }

    return {
      valid: errors.length === 0,
      errors,
      data: errors.length === 0 ? data : null
    };
  }
}

// Usage
const userSchema = {
  name: {
    type: 'string',
    validate: (v) => v.length > 0,
    message: 'Name cannot be empty'
  },
  email: {
    type: 'string',
    validate: (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v),
    message: 'Invalid email format'
  },
  age: {
    type: 'number',
    validate: (v) => v >= 0 && v <= 150,
    message: 'Age must be between 0 and 150'
  }
};

const validator = new DataValidator(userSchema);

const result = validator.validate({
  name: 'John',
  email: '[email protected]',
  age: 30
});

console.log(result);
// { valid: true, errors: [], data: { ... } }

Best Practices

  1. Use Object.freeze for constants:
    // ✅ Good
    const CONSTANTS = Object.freeze({
      MAX_SIZE: 100,
      MIN_SIZE: 10
    });
    
    // ❌ Bad
    const CONSTANTS = {
      MAX_SIZE: 100,
      MIN_SIZE: 10
    };
    ```javascript
    
  2. Use descriptors for controlled properties:
    // ✅ Good
    Object.defineProperty(obj, 'id', {
      value: 123,
      writable: false
    });
    
    // ❌ Bad
    obj.id = 123;
    ```javascript
    
  3. Deep clone when needed:
    // ✅ Good
    const cloned = deepClone(original);
    
    // ❌ Bad
    const cloned = { ...original };
    ```javascript
    

Common Mistakes

  1. Shallow freeze doesn’t freeze nested objects:
    // ❌ Bad
    const obj = { nested: { value: 1 } };
    Object.freeze(obj);
    obj.nested.value = 2; // Works!
    
    // ✅ Good
    deepFreeze(obj);
    obj.nested.value = 2; // Fails
    ```javascript
    
  2. Modifying cloned objects affects original:
    // ❌ Bad
    const clone = { ...original };
    clone.nested.value = 2; // Affects original
    
    // ✅ Good
    const clone = deepClone(original);
    clone.nested.value = 2; // Doesn't affect original
    

Summary

Advanced object manipulation provides fine-grained control. Key takeaways:

  • Property descriptors control behavior
  • Freezing prevents modifications
  • Deep cloning creates independent copies
  • Transformations enable data manipulation
  • Immutability improves reliability
  • Composition enables flexibility
  • Validation ensures data integrity

Next Steps

Resources

Comments

Share this article

Scan to read on mobile