Hone logo
Hone
Problems

JavaScript Nested Object Iterator

This challenge requires you to build a custom iterator in JavaScript that can traverse through a deeply nested object, yielding each non-object value it encounters. This is a common task when you need to process all primitive values within a complex data structure, such as flattening data for storage, searching for specific values, or performing bulk operations.

Problem Description

Your task is to create a JavaScript function that returns an iterator. This iterator should be able to traverse a nested JavaScript object and yield all primitive values (strings, numbers, booleans, null, undefined) in the order they are encountered during a depth-first traversal.

Key Requirements:

  • The function should accept a single argument: the nested object to iterate over.
  • The returned object must adhere to the JavaScript iterator protocol, meaning it must have a next() method.
  • The next() method should return an object with two properties: value (the next primitive value found) and done (a boolean indicating if the iteration is complete).
  • The traversal should be depth-first. When encountering an object, the iterator should descend into it before moving to sibling properties.
  • Only primitive values should be yielded. Nested objects and arrays themselves should not be yielded as values.
  • Circular references within the object should be handled gracefully, preventing infinite loops.

Expected Behavior:

When iterator.next() is called repeatedly, it should yield each primitive value found in the object. Once all primitive values have been yielded, subsequent calls to next() should return { value: undefined, done: true }.

Edge Cases to Consider:

  • Empty objects.
  • Objects containing only other objects (no primitive values).
  • Objects with deeply nested structures.
  • Objects containing circular references.
  • Objects with properties whose values are null or undefined.

Examples

Example 1:

Input: {
  a: 1,
  b: {
    c: "hello",
    d: {
      e: true
    }
  },
  f: null
}
Output:
{ value: 1, done: false }
{ value: "hello", done: false }
{ value: true, done: false }
{ value: null, done: false }
{ value: undefined, done: true }

Explanation: The iterator first visits a (value 1), then descends into b. Inside b, it visits c ("hello"), then descends into d. Inside d, it visits e (true). After d is fully explored, it returns to b's level and visits f (null). Since there are no more properties or nested objects to explore, the iteration is done.

Example 2:

Input: {
  user: {
    name: "Alice",
    address: {
      street: "123 Main St",
      city: "Anytown"
    }
  },
  settings: {
    theme: "dark"
  }
}
Output:
{ value: "Alice", done: false }
{ value: "123 Main St", done: false }
{ value: "Anytown", done: false }
{ value: "dark", done: false }
{ value: undefined, done: true }

Explanation: The traversal goes user.name, then user.address.street, then user.address.city, and finally settings.theme.

Example 3 (Circular Reference):

Input:
let obj = { data: 1 };
obj.self = obj; // Creating a circular reference
Output:
{ value: 1, done: false }
{ value: undefined, done: true }

Explanation: The iterator encounters data (value 1). When it tries to process self, it detects the circular reference and avoids infinite recursion, effectively treating it as already visited or an endpoint for traversal.

Constraints

  • The input object can have any depth of nesting.
  • The input will always be a JavaScript object (or potentially null/undefined, which should be handled as empty).
  • The performance of the iterator should be reasonable for typical object sizes and depths, avoiding excessive memory consumption for tracking visited nodes.

Notes

  • You'll need to manage a way to keep track of the current position within the nested structure. A stack data structure is often useful for depth-first traversal.
  • Consider how you will detect and handle circular references to prevent infinite loops. You might need to keep track of objects already visited during the traversal.
  • Helper functions might be useful to encapsulate logic for checking if a value is a primitive or a plain object.
Loading editor...
javascript