对象扁平化

将嵌套的对象结构转换为扁平的键值对形式,使用点号连接嵌套的键名

问题

对象扁平化是指将一个多层嵌套的对象转换为单层对象,其中嵌套的键通过特定分隔符(通常是点号)连接起来。例如:

// 输入
{
  a: 1,
  b: {
    c: 2,
    d: {
      e: 3
    }
  }
}

// 输出
{
  'a': 1,
  'b.c': 2,
  'b.d.e': 3
}

这在处理表单数据、配置文件或需要将复杂对象结构转换为简单键值对时非常有用。

解答

/**
 * 对象扁平化
 * @param {Object} obj - 需要扁平化的对象
 * @param {string} prefix - 键名前缀
 * @param {Object} result - 结果对象
 * @returns {Object} 扁平化后的对象
 */
function flattenObject(obj, prefix = '', result = {}) {
  // 遍历对象的所有键
  for (let key in obj) {
    // 只处理对象自身的属性
    if (obj.hasOwnProperty(key)) {
      // 构造新的键名
      const newKey = prefix ? `${prefix}.${key}` : key;
      
      // 判断值的类型
      if (obj[key] !== null && typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
        // 如果是对象(非数组、非null),递归处理
        flattenObject(obj[key], newKey, result);
      } else {
        // 如果是基本类型或数组,直接赋值
        result[newKey] = obj[key];
      }
    }
  }
  
  return result;
}

// 方法二:使用迭代方式实现
function flattenObjectIterative(obj) {
  const result = {};
  const stack = [{ value: obj, prefix: '' }];
  
  while (stack.length > 0) {
    const { value, prefix } = stack.pop();
    
    for (let key in value) {
      if (value.hasOwnProperty(key)) {
        const newKey = prefix ? `${prefix}.${key}` : key;
        
        if (value[key] !== null && typeof value[key] === 'object' && !Array.isArray(value[key])) {
          // 将嵌套对象压入栈中
          stack.push({ value: value[key], prefix: newKey });
        } else {
          result[newKey] = value[key];
        }
      }
    }
  }
  
  return result;
}

使用示例

// 示例1:基本嵌套对象
const obj1 = {
  a: 1,
  b: {
    c: 2,
    d: {
      e: 3
    }
  }
};
console.log(flattenObject(obj1));
// 输出: { a: 1, 'b.c': 2, 'b.d.e': 3 }

// 示例2:包含数组的对象
const obj2 = {
  name: 'John',
  info: {
    age: 30,
    hobbies: ['reading', 'coding'],
    address: {
      city: 'Beijing',
      street: 'Main St'
    }
  }
};
console.log(flattenObject(obj2));
// 输出: {
//   name: 'John',
//   'info.age': 30,
//   'info.hobbies': ['reading', 'coding'],
//   'info.address.city': 'Beijing',
//   'info.address.street': 'Main St'
// }

// 示例3:包含null和undefined
const obj3 = {
  a: null,
  b: undefined,
  c: {
    d: null,
    e: 0,
    f: false
  }
};
console.log(flattenObject(obj3));
// 输出: {
//   a: null,
//   b: undefined,
//   'c.d': null,
//   'c.e': 0,
//   'c.f': false
// }

// 示例4:空对象
const obj4 = {
  a: 1,
  b: {},
  c: {
    d: 2
  }
};
console.log(flattenObject(obj4));
// 输出: { a: 1, 'c.d': 2 }

关键点

  • 递归遍历:使用递归方式遍历对象的每一层,将嵌套结构展开

  • 类型判断:需要准确判断值的类型,区分对象、数组、null 和基本类型

    • 使用 typeof obj[key] === 'object' 判断是否为对象类型
    • 使用 Array.isArray() 排除数组
    • 使用 !== null 排除 null(因为 typeof null 也是 ‘object’)
  • 键名拼接:使用点号(.)连接父级键名和当前键名,形成完整路径

  • 边界处理

    • 空对象不会产生键值对
    • 数组作为值直接保留,不进行展开
    • null、undefined 等特殊值需要正确处理
  • 性能优化:对于深层嵌套的大对象,可以考虑使用迭代方式代替递归,避免栈溢出

  • 扩展性:可以根据需求自定义分隔符、处理数组的方式等