修改对象中的第 n 个嵌套 属性

Modify nth nested property in object

这基本上是一个适用于任何编程语言的逻辑问题,我只是碰巧用 JavaScript 来做这个

基本上想象我有一些像

这样的对象
{Props:"stuff", 
    other: {
        stuff: "etc",
        other: {
            Extra: "filler",
            other: {}
        }
    }
}

本质上,这是一个具有未知数量额外属性的对象,但每个子对象都有一个嵌套的 属性,名称相同“在本例中为“其他”),深度可能是无限,这意味着根对象中可能有 n 个对象,每个子对象都包含在另一个对象中,该对象的键相同(同样在本例中为“其他”)

听起来可能比实际情况更复杂,但基本上 属性 other 是在根对象中嵌套和重复的

我想做一个函数来打破给定深度量 (n) 的嵌套链,从而将所有进一步嵌套的对象设置为一个值

这听起来可能很复杂,但基本上,在上面的示例中,对象的“其他”属性 的嵌套深度为 3,假设我想打破深度索引 2 处的深度,从而将对象更改为只有一个嵌套的“其他”属性,设置为某个值(例如数字或字符串,例如“5”),而不是设置为另一个也包含另一个的对象属性(以及潜在的附加属性等)

那么我如何创建一个接受基础对象的函数,其中包含未知数量的嵌套属性 Ruth key k,并使其成为 return 一个新对象(或修改原始的,但我更喜欢 return 一个新的)它几乎与基础对象相同,除了在深度索引 n 处,嵌套的 属性 和键 k 将被设置为值 v,而不是继续在它的未知附加嵌套深度

的链中

我什至无法理解如何做到这一点,我总是回到设置索引 k 等于根函数的 属性,但传递 属性本身作为参数代替原始根对象(基本上是递归)除非输入对象不包含 属性 k,在这种情况下只有 属性 本身是 returned,但这只是对return最内层嵌套属性的递归方法,但不知该怎么做才能达到上述结果,这又是一个新对象几乎与基础对象 Ruth 相同,除了它嵌套 属性,深度 n 的键 k 被设置为值 v,我完全不知道在哪里甚至开始

我们可以像这样使用递归函数

let obj = {Props:"stuff", 
    other: {
        stuff: "etc",
        other: {
            Extra: "filler",
            other: {
                stuff: "abc",
                other:{
                    stuff: "acbh",
                    other: {}
                }
            }
        }
    }
};

function normalizeObjectDepth(obj, level, curr_level, key){
    if(curr_level==level-1){
        obj.other = key;
        return;
    }
    normalizeObjectDepth(obj.other, level, curr_level+1, key);
}

normalizeObjectDepth(obj, 3, 0, 5);

console.log(obj);

您可以使用 reduce 方法创建递归函数,该函数将检查当前递归级别是否小于目标级别,并根据该级别继续递归或将值设置为所需级别。

const data = {
  Props: "stuff",
  other: {
    stuff: "etc",
    other: {
      Extra: "filler",
      other: {}
    }
  }
}

function modify(obj, key, lvl, val, clvl = 0) {
  return Object.entries(obj).reduce((r, [k, v]) => {
    if (k === key) {
      if (clvl < lvl) {
        r[k] = modify(v, key, lvl, val, clvl + 1)
      } else {
        r[k] = val
      }
    } else {
      r[k] = v;
    }

    return r;
  }, {})
}

const result = modify(data, 'other', 2, 'my value');
console.log(result)

简单 for...in 循环的解决方案。

const data = {
  Props: "stuff",
  other: {
    stuff: "etc",
    other: {
      Extra: "filler",
      other: {
        random: 'foo',
        other: 'random'
      }
    }
  }
}

function modify(obj, key, lvl, val, clvl = 0) {
  const result = {}

  for (let k in obj) {
    if (k === key) {
      if (clvl < lvl) {
        result[k] = modify(obj[k], key, lvl, val, clvl + 1)
      } else {
        result[k] = val
      }
    } else {
      result[k] = obj[k]
    }
  }

  return result

}

const result = modify(data, 'other', 2, 'random');
console.log(result)

这确保了深度克隆,因此不会更改原始对象。唯一的“高级”部分是 JSON.parse(JSON.stringify(...)),但可以用任何深度复制方法代替,或者您可以在使用函数 [=11 之前简单地进行深度复制=]

const modifyNthKey = (obj, key, value, n) => {
  let i = 0
  let newObj = JSON.parse(JSON.stringify(obj))
  let tmpObj = newObj
  while (i < n) {
    console.log('typeof: ' + typeof tmpObj[`${key}`])
    if (typeof tmpObj[`${key}`] !== 'object') {
      throw Error('unable to access key. Parent not object')
    }
    tmpObj = tmpObj[`${key}`]
    i++
  }

  tmpObj[`${key}`] = value
  return newObj
}

这是一个相当简单的递归版本。它不会改变您的输入,而是 returns 一个新结构:

const modify = (obj, key, value, depth) => ({
  ...obj, 
  [key]: depth <= 0 ? value : modify (obj [key], key, value, depth - 1)
})

const data = {Props: "stuff", other: {stuff: "etc", other: {Extra: "filler", other: {}}}}

console .log (modify (data, 'other', 'new value', 2))
//~> {Props: "stuff", other: {stuff: "etc", other: {Extra: "filler", other:"new value"}}}

console .log (modify (data, 'other', 'new value', 1))
//~> {Props: "stuff", other: {stuff: "etc", other: "new value"}}

console .log (modify (data, 'other', 'new value', 0))
//~> {Props: "stuff", other: "new value"}
.as-console-wrapper {max-height: 100% !important; top: 0}

本质上我们只是复制对象,将目标 属性 ('other') 设置为新值(如果我们在递归结束时,当 depth为 0) 或使用 object[key]depth -1 的递归调用结果(否则。)

请注意,我们不进行完整克隆,因此其他节点可能会通过引用共享。