Для каждого вложенного объекта нужно добавить свойство 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 при каждом входе во вложенность; это реализуется рекурсией (с мутацией или без) либо итеративно через стек.