← Назад к вопросам

Flatten вложенного объекта

2.2 Middle🔥 121 комментариев
#Node.js и JavaScript#Алгоритмы и структуры данных

Условие

Напишите функцию flattenObject, которая преобразует вложенный объект в плоский, используя точечную нотацию для ключей:

function flattenObject(obj, prefix = "") {
  // Ваш код
}

// Пример:
const input = {
  a: 1,
  b: {
    c: 2,
    d: {
      e: 3
    }
  },
  f: 4
};

console.log(flattenObject(input));
// {
//   "a": 1,
//   "b.c": 2,
//   "b.d.e": 3,
//   "f": 4
// }

Что проверяется

  • Рекурсия
  • Работа с объектами
  • Итерация по свойствам объекта

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Решение

Базовая реализация

function flattenObject(
  obj: Record<string, any>,
  prefix: string = ""
): Record<string, any> {
  const result: Record<string, any> = {};

  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      const value = obj[key];
      const newKey = prefix ? `${prefix}.${key}` : key;

      if (value !== null && typeof value === "object" && !Array.isArray(value)) {
        // Рекурсивно обрабатываем вложенные объекты
        Object.assign(result, flattenObject(value, newKey));
      } else {
        // Добавляем примитивные значения
        result[newKey] = value;
      }
    }
  }

  return result;
}

Как это работает

Принцип работы:

  1. Итерируем по ключам — проходим по каждому свойству объекта
  2. Формируем полный ключ — собираем путь через точку (a.b.c)
  3. Проверяем значение — если объект, рекурсивно разворачиваем
  4. Добавляем в результат — примитивные значения добавляем сразу

Пошаговый процесс для примера:

Вход: { a: 1, b: { c: 2, d: { e: 3 } }, f: 4 }

Шаг 1: Ключ "a"
  ├─ value = 1 (примитив)
  ├─ newKey = "a"
  └─ result["a"] = 1

Шаг 2: Ключ "b"
  ├─ value = { c: 2, d: { e: 3 } } (объект)
  ├─ newKey = "b"
  ├─ Рекурсия: flattenObject(value, "b")
  │  ├─ Ключ "c"
  │  │  ├─ newKey = "b.c"
  │  │  └─ result["b.c"] = 2
  │  └─ Ключ "d"
  │     ├─ value = { e: 3 } (объект)
  │     ├─ newKey = "b.d"
  │     ├─ Рекурсия: flattenObject(value, "b.d")
  │     │  └─ result["b.d.e"] = 3

Шаг 3: Ключ "f"
  ├─ value = 4 (примитив)
  ├─ newKey = "f"
  └─ result["f"] = 4

Результат: { "a": 1, "b.c": 2, "b.d.e": 3, "f": 4 }

Пример использования

const input = {
  a: 1,
  b: {
    c: 2,
    d: {
      e: 3
    }
  },
  f: 4
};

const flattened = flattenObject(input);
console.log(flattened);
// {
//   "a": 1,
//   "b.c": 2,
//   "b.d.e": 3,
//   "f": 4
// }

Версия с поддержкой массивов

function flattenObject(
  obj: Record<string, any>,
  prefix: string = ""
): Record<string, any> {
  const result: Record<string, any> = {};

  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      const value = obj[key];
      const newKey = prefix ? `${prefix}.${key}` : key;

      if (Array.isArray(value)) {
        // Обрабатываем массивы
        value.forEach((item, index) => {
          const arrayKey = `${newKey}[${index}]`;
          if (typeof item === "object" && item !== null) {
            Object.assign(result, flattenObject(item, arrayKey));
          } else {
            result[arrayKey] = item;
          }
        });
      } else if (value !== null && typeof value === "object") {
        // Рекурсивно обрабатываем объекты
        Object.assign(result, flattenObject(value, newKey));
      } else {
        // Добавляем примитивные значения
        result[newKey] = value;
      }
    }
  }

  return result;
}

// Пример:
const input = {
  a: 1,
  b: [
    { c: 2 },
    { c: 3 }
  ]
};

flattenObject(input);
// {
//   "a": 1,
//   "b[0].c": 2,
//   "b[1].c": 3
// }

Версия с использованием reduce

function flattenObject(
  obj: Record<string, any>,
  prefix: string = ""
): Record<string, any> {
  return Object.keys(obj).reduce((result, key) => {
    const value = obj[key];
    const newKey = prefix ? `${prefix}.${key}` : key;

    if (value !== null && typeof value === "object" && !Array.isArray(value)) {
      return { ...result, ...flattenObject(value, newKey) };
    }

    return { ...result, [newKey]: value };
  }, {});
}

Версия с поддержкой разделителя

function flattenObject(
  obj: Record<string, any>,
  separator: string = ".",
  prefix: string = ""
): Record<string, any> {
  const result: Record<string, any> = {};

  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      const value = obj[key];
      const newKey = prefix ? `${prefix}${separator}${key}` : key;

      if (value !== null && typeof value === "object" && !Array.isArray(value)) {
        Object.assign(result, flattenObject(value, separator, newKey));
      } else {
        result[newKey] = value;
      }
    }
  }

  return result;
}

// Пример с разными разделителями:
const obj = { a: { b: { c: 1 } } };

flattenObject(obj, ".");  // { "a.b.c": 1 }
flattenObject(obj, "_");  // { "a_b_c": 1 }
flattenObject(obj, "/");  // { "a/b/c": 1 }

Версия с обратным преобразованием (unflatten)

function unflattenObject(
  obj: Record<string, any>,
  separator: string = "."
): Record<string, any> {
  const result: Record<string, any> = {};

  for (const key in obj) {
    const value = obj[key];
    const keys = key.split(separator);
    let current = result;

    for (let i = 0; i < keys.length - 1; i++) {
      const k = keys[i];
      if (!(k in current)) {
        current[k] = {};
      }
      current = current[k];
    }

    current[keys[keys.length - 1]] = value;
  }

  return result;
}

// Пример:
const flattened = { "a.b.c": 1, "a.b.d": 2 };
const unflattened = unflattenObject(flattened);
console.log(unflattened);
// { a: { b: { c: 1, d: 2 } } }

Тестирование

import { describe, it, expect } from 'vitest';

describe('flattenObject', () => {
  it('должен разворачивать простой объект', () => {
    const input = { a: 1, b: 2 };
    const expected = { a: 1, b: 2 };
    expect(flattenObject(input)).toEqual(expected);
  });

  it('должен разворачивать вложенный объект', () => {
    const input = { a: 1, b: { c: 2, d: { e: 3 } }, f: 4 };
    const expected = { a: 1, "b.c": 2, "b.d.e": 3, f: 4 };
    expect(flattenObject(input)).toEqual(expected);
  });

  it('должен обрабатывать глубокие вложения', () => {
    const input = { a: { b: { c: { d: { e: 1 } } } } };
    const expected = { "a.b.c.d.e": 1 };
    expect(flattenObject(input)).toEqual(expected);
  });

  it('должен обрабатывать разные типы значений', () => {
    const input = {
      str: "hello",
      num: 42,
      bool: true,
      nil: null,
      nested: { arr: [1, 2, 3] }
    };
    const result = flattenObject(input);
    expect(result.str).toBe("hello");
    expect(result.num).toBe(42);
    expect(result.bool).toBe(true);
    expect(result.nil).toBeNull();
    expect(result["nested.arr"]).toEqual([1, 2, 3]);
  });

  it('должен обрабатывать пустые объекты', () => {
    const input = { a: {}, b: 1 };
    const result = flattenObject(input);
    expect(result).toEqual({ b: 1 });
  });
});

Практические применения

1. Конфигурационные файлы:

const config = {
  database: {
    host: "localhost",
    port: 5432,
    credentials: {
      username: "admin",
      password: "secret"
    }
  }
};

const flattened = flattenObject(config);
// {
//   "database.host": "localhost",
//   "database.port": 5432,
//   "database.credentials.username": "admin",
//   "database.credentials.password": "secret"
// }

2. Преобразование для API:

const userData = {
  profile: {
    name: "John",
    contact: {
      email: "john@example.com",
      phone: "123-456-7890"
    }
  }
};

const formData = flattenObject(userData);
// Удобнее отправлять в URL-параметрах или форме

3. Логирование с контекстом:

const error = {
  message: "Invalid input",
  context: {
    field: "email",
    validation: {
      rule: "required",
      value: ""
    }
  }
};

const flattened = flattenObject(error);
console.log(flattened);
// Проще логировать структурированные данные

Сложность

  • Временная сложность: O(n), где n — общее количество свойств
  • Пространственная сложность: O(n), так как создаём новый объект

Альтернативный подход с рекурсией

function flattenObject(
  obj: Record<string, any>,
  prefix: string = "",
  result: Record<string, any> = {}
): Record<string, any> {
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      const value = obj[key];
      const newKey = prefix ? `${prefix}.${key}` : key;

      if (value !== null && typeof value === "object" && !Array.isArray(value)) {
        flattenObject(value, newKey, result);
      } else {
        result[newKey] = value;
      }
    }
  }

  return result;
}

Этот подход модифицирует результат в месте, что немного более эффективно по памяти.