Для каждого вложенного объекта нужно добавить свойство level, которое равняется числу - номер вложенности

Дан объект:

const object = {
  a: {
    d: {
      h: 4,
    },
    e: 2,
  },
  b: 1,
  c: {
    f: {
      g: 3,
      k: {},
    },
  },
};

Необходимо для каждого вложенного объекта нужно добавить свойство level, которое равняется числу - номер вложенности. Если значение свойства будет не объект, то ничего не добавлять

То есть, функция addlevelInObj(object) принимает объект object, описаный выше и должна возвратить объект вида:

{
  a: {
    level: 1,
    d: {
      level: 2,
      h: 4,
    },
    e: 2,
  },
  b: 1,
  c: {
    level: 1,
    f: {
      level: 2,
      g: 3,
      k: {
        level: 3,
      },
    },
  },
}

Примеры:

// Параметр функции:
{
  a: {
    d: {
      h: 4,
    },
    e: 2,
  },
  b: 1,
  c: {
    f: {
      g: 3,
      k: {},
    },
  },
}

// Возвращаемое значение функции:
{
  a: {
    level: 1,
    d: {
      level: 2,
      h: 4,
    },
    e: 2,
  },
  b: 1,
  c: {
    level: 1,
    f: {
      level: 2,
      g: 3,
      k: {
        level: 3,
      },
    },
  },
}
// Параметр функции:
{
  a: {
    d: 4,
  },
  b: 1,
  c: {
    k: {},
  },
}

// Возвращаемое значение функции:
{
  a: {
    level: 1,
    d: 4,
  },
  b: 1,
  c: {
    level: 1,
    k: {
      level: 2,
    },
  },
}

P.s. результаты функции и тестовых данных сравниваются с помощью результата оборачивания объектов в JSON.stringify, поэтому соблюдение порядка полей важно.

Теория и разбор

Что считается объектом в этой задаче

Для задачи требуется добавлять level только тем значениям, которые являются вложенными объектами (обычными объектами), а не примитивами.
Практичная проверка для “обычного объекта” в рамках задания: value !== null && typeof value === "object" && !Array.isArray(value).

Почему проверка именно такая:

  • typeof value === "object" отсеивает числа, строки, булевы значения, undefined, функции.
  • value !== null обязателен, так как null технически возвращает тип "object", но по смыслу не является объектом с полями.
  • !Array.isArray(value) исключает массивы, так как по условию требуется работать с вложенными объектами, а не с коллекциями (если массивы тоже нужно обрабатывать, это условие можно убрать, но тогда изменится смысл задания).

Как обход “в глубину” присваивает level

level — это число, показывающее сколько “шагов вниз” сделано от корня до текущего объекта:

  • Дочерние объекты корня получают level: 1.
  • Их дочерние объекты получают level: 2.
  • И так далее.

Текстовая схема для примера:

root
├─ a (level 1)
│  ├─ d (level 2)
│  │  └─ h = 4 (не объект)
│  └─ e = 2 (не объект)
├─ b = 1 (не объект)
└─ c (level 1)
   └─ f (level 2)
      ├─ g = 3 (не объект)
      └─ k (level 3)

Рекурсивная идея:

  • Функция получает объект и текущий level.
  • Сначала (или в конце) записывается obj.level = level.
  • Затем перебираются свойства; если значение — объект, делается рекурсивный вызов с level + 1.

Итеративная идея со стеком:

  • Вместо “вызова самой себя” данные о том, что нужно обработать позже, кладутся в массив stack.
  • Из stack по одному извлекаются задачи “обработать объект src, записывать в dst, с указанным level”.

Почему примитивам level не добавляется

Примитивы (число, строка, булево значение, undefined, null) не имеют структуры “ключ → значение”, в которую логично добавлять level.
Поэтому на свойствах вида b: 1, h: 4, e: 2, g: 3 никаких изменений не выполняется.

Мутация и немутирующий подход

При немутирующем подходе (Вариант 1 и 3) входной объект остаётся прежним, а результат строится заново. Это удобно, когда требуется предсказуемость и отсутствие побочных эффектов.
При мутации (Вариант 2) функция изменяет входной объект напрямую, что может быть быстрее по памяти, но требует аккуратности в коде, где входной объект используется повторно.

Таблица различий:

ВариантИзменение входаГлубокая вложенностьРасход памяти
1 (рекурсия, новый объект)НетВозможны ограничения стекаВыше
2 (рекурсия, in-place)ДаВозможны ограничения стекаНиже
3 (итерация, стек, новый объект)НетОбычно устойчивееСредний

Частые крайние случаи и улучшения

Пустой объект {} тоже является объектом, поэтому он должен получить level (как в примере k: {}k: { level: 3 }).
Если во входных данных уже есть поле level, то приведённые решения перезапишут его корректным значением глубины (это часто ожидаемо, но при необходимости можно договориться о другом поведении).

Если во входном объекте потенциально возможны циклические ссылки (например, obj.a = obj), рекурсивный и итеративный обход без защиты уйдёт в бесконечный цикл. Для защиты обычно добавляется WeakSet посещённых объектов.

Пример защиты от циклов (немутирующий рекурсивный вариант):

function addlevelInObjSafe(input) {
  const isPlainObject = (value) =>
    value !== null && typeof value === "object" && !Array.isArray(value);

  const seen = new WeakSet();

  const walk = (node, level) => {
    if (!isPlainObject(node)) return node;
    if (seen.has(node)) return node; // или выбросить ошибку, в зависимости от требований
    seen.add(node);

    const out = {};
    for (const [key, value] of Object.entries(node)) {
      out[key] = isPlainObject(value) ? walk(value, level + 1) : value;
    }
    out.level = level;
    return out;
  };

  const result = {};
  for (const [key, value] of Object.entries(input)) {
    result[key] = isPlainObject(value) ? walk(value, 1) : value;
  }
  return result;
}

Кратко: необходимо выполнить обход объекта в глубину и добавлять level только тем значениям, которые являются вложенными обычными объектами (не null и не массив), увеличивая глубину на 1 при каждом входе во вложенность; это реализуется рекурсией (с мутацией или без) либо итеративно через стек.