实现 getValue/setValue 函数来获取path对应的值

通过路径字符串安全地获取和设置对象深层嵌套属性的值

问题

在实际开发中,我们经常需要访问对象的深层嵌套属性,例如 obj.a.b.c。但直接访问可能会因为中间某个属性不存在而报错。我们需要实现两个工具函数:

  • getValue(obj, path, defaultValue): 根据路径安全地获取对象属性值
  • setValue(obj, path, value): 根据路径设置对象属性值

解答

/**
 * 根据路径获取对象属性值
 * @param {Object} obj - 目标对象
 * @param {String} path - 属性路径,支持 'a.b.c' 或 'a[0].b' 格式
 * @param {*} defaultValue - 默认值
 * @returns {*} 返回对应路径的值,如果不存在则返回默认值
 */
function getValue(obj, path, defaultValue = undefined) {
  // 将路径转换为数组,支持 'a.b.c' 和 'a[0].b' 两种格式
  const keys = path.replace(/\[(\d+)\]/g, '.$1').split('.');
  
  let result = obj;
  
  // 逐层访问对象属性
  for (let key of keys) {
    // 如果当前层级为 null 或 undefined,返回默认值
    if (result == null) {
      return defaultValue;
    }
    result = result[key];
  }
  
  // 如果最终结果为 undefined,返回默认值
  return result === undefined ? defaultValue : result;
}

/**
 * 根据路径设置对象属性值
 * @param {Object} obj - 目标对象
 * @param {String} path - 属性路径,支持 'a.b.c' 或 'a[0].b' 格式
 * @param {*} value - 要设置的值
 * @returns {Object} 返回修改后的对象
 */
function setValue(obj, path, value) {
  // 将路径转换为数组
  const keys = path.replace(/\[(\d+)\]/g, '.$1').split('.');
  const lastKey = keys.pop();
  
  let current = obj;
  
  // 逐层创建不存在的对象
  for (let i = 0; i < keys.length; i++) {
    const key = keys[i];
    const nextKey = keys[i + 1];
    
    // 如果当前属性不存在或不是对象,则创建新对象
    if (current[key] == null || typeof current[key] !== 'object') {
      // 判断下一个 key 是否为数字,决定创建对象还是数组
      current[key] = /^\d+$/.test(nextKey) ? [] : {};
    }
    
    current = current[key];
  }
  
  // 设置最终的值
  current[lastKey] = value;
  
  return obj;
}

使用示例

// 测试对象
const obj = {
  a: {
    b: {
      c: 'hello'
    }
  },
  arr: [1, 2, { name: 'test' }]
};

// getValue 示例
console.log(getValue(obj, 'a.b.c')); // 'hello'
console.log(getValue(obj, 'a.b.d', 'default')); // 'default'
console.log(getValue(obj, 'arr[0]')); // 1
console.log(getValue(obj, 'arr[2].name')); // 'test'
console.log(getValue(obj, 'x.y.z', 'not found')); // 'not found'

// setValue 示例
const newObj = {};

setValue(newObj, 'a.b.c', 100);
console.log(newObj); // { a: { b: { c: 100 } } }

setValue(newObj, 'arr[0]', 'first');
console.log(newObj); // { a: { b: { c: 100 } }, arr: ['first'] }

setValue(newObj, 'user.info.name', 'John');
console.log(newObj); 
// { a: { b: { c: 100 } }, arr: ['first'], user: { info: { name: 'John' } } }

// 修改已存在的值
setValue(obj, 'a.b.c', 'world');
console.log(obj.a.b.c); // 'world'

关键点

  • 路径解析:使用正则表达式 /\[(\d+)\]/g 将数组索引格式 [0] 转换为点号格式 .0,统一处理
  • 安全访问:在 getValue 中使用 == null 判断,同时处理 nullundefined 的情况
  • 默认值处理:当路径不存在或值为 undefined 时返回默认值
  • 自动创建对象:在 setValue 中,如果路径上的对象不存在,自动创建中间对象
  • 智能类型判断:根据下一个 key 是否为数字,决定创建对象还是数组
  • 链式路径:支持 a.b.ca[0].b 两种路径格式的混合使用
  • 不可变性考虑:如果需要不修改原对象,可以在 setValue 开始时先深拷贝对象