微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

如何基于键值项列表更新现有的嵌套对象属性

如何解决如何基于键值项列表更新现有的嵌套对象属性

我有一个包含键和值的已更改值的数组。因此,想法是循环遍历并根据键名更新新值。但是问题是我不知道密钥属于哪个深度。我猜可能有一些使用lodash的解决方案,但我不知道哪个解决方案。无论如何,除了我最初的尝试之外,还有更好的方法,欢迎提出任何建议。谢谢

function updatenestedobj({ obj,nodeId,value,accumulator}){
    Object.keys(obj).forEach(key => {
        let oldVal = obj[key]
        accumulator[nodeId] = value
        const hasChild = typeof oldVal === 'object'
        if (hasChild) 
           accumulator[nodeId] = updatenestedobj({ obj: oldVal,accumulator })
       
    })
}

let changednodes = [
    { id: 'grand_child_1',value: 'new grand_child_1 value' },{ id: 'node_child_1',value: 'new node_child_1 value' },]

let objToBeUpdated = {
        root_node: {
            node_child: {
                grand_child: 'grand_child value',grand_child_1: 'grand_child_1 value',},node_child_1: 'node_child_1 value',}

let accumulator = {}
let result= {}

changednodes.forEach(node => {
    updatenestedobj({
        obj: objToBeUpdated,nodeId: node.id,value: node.value,accumulator: accumulator,})   
})
result.root_node=accumulator
console.log(result) 

预期结果应如下所示:

{
    root_node: {
        node_child: {
            grand_child: 'grand_child value',grand_child_1: 'new grand_child_1 value',node_child_1: 'new node_child_1 value',} 

解决方法

我同意@Scott的观点,我们应该旨在将程序划分为更明智的部分。我们可以编写递归update来接受输入树t,要更新的属性id和要设置的值。

使用数学归纳法,我们可以合理地构建程序。下面的编号点对应于程序中的编号注释-

  1. 如果要更新的属性id与键k匹配,则返回要设置的值value
  2. (归纳)id与密钥不匹配。如果值v是对象,则递归update
  3. (归纳)id与键不匹配,并且v不是对象。返回未修改的值
const update = (t,{ id,value }) =>
  map
    ( t,(v,k) =>
        id === k
          ? value                      // 1
      : Object(v) === v
          ? update(v,value })   // 2
      : v                              // 3
    )

这取决于通用的map函数,该函数允许对对象进行映射-

const map = (t,f) =>
  Object.fromEntries(Object.entries(t).map(([k,v]) => [k,f(v,k)]))

给出原始inputchanges的列表-

const input =
  {root_node: {node_child: {grand_child: 'grand_child value',grand_child_1: 'grand_child_1 value'},node_child_1: 'node_child_1 value'}}

const changes =
  [{id: 'grand_child_1',value: 'new grand_child_1 value'},{id: 'node_child_1',value: 'new node_child_1 value'}]

我们可以使用result轻松获得reduce-

const result =
  changes.reduce(update,input)
  
console.log(result)

输出-

{
  "root_node": {
    "node_child": {
      "grand_child": "grand_child value","grand_child_1": "new grand_child_1 value"    // <--
    },"node_child_1": "new node_child_1 value"        // <--
  }
}

展开下面的代码片段,以在浏览器中验证结果-

const map = (t,k)]))
  
const update = (t,k) =>
        id === k
          ?  value 
      : Object(v) === v
          ? update(v,value })
      : v 
    )

const input =
  {root_node: {node_child: {grand_child: 'grand_child value',value: 'new node_child_1 value'}]

const result =
  changes.reduce(update,input)
  
console.log(result)

,

const obj = {
    root_node: {
        node_child: {
            grand_child: "grand_child value",grand_child_1: "grand_child_1 value"
        },node_child_1: "node_child_1 value"
    }
};
const changedNodes = [
    {
        id: "grand_child_1",value: "new grand_child_1 value"
    },{
        id: "node_child_1",value: "new node_child_1 value"
    }
];

function update(obj,changes) {
    return Object.fromEntries(Object.entries(obj).map(([key,value]) => {
        if (value && typeof value === "object") {
            return [
                key,update(value,changes)
            ];
        }
        if (changes.hasOwnProperty(key)) {
            return [
                key,changes[key]
            ];
        }
        return [
            key,value
        ];
    }));
}

console.log(update(obj,changedNodes.reduce((obj,{id,value}) => Object.assign(obj,{[id]: value}),{})));

,

简单递归,沿着树上走,并遍历对象。

var myObject = {
    root_node: {
        node_child: {
            grand_child: 'grand_child',grand_child_1: 'new grand_child_1',},node_child_1: 'new node_child_1',} 

let changedNodes = [
    { id: 'grand_child_1',value: 'new grand_child_1 UPDATED1' },{ id: 'node_child_1',value: 'new node_child_1 UPDATED2' },]

function updateNode(obj,key,value) {
  if (obj[key] !== undefined) { // if value can be undefined,this would need to change.
    obj[key] = value;
    return true;
  } else {
    for (const prop in obj) {
      if (obj[prop] && typeof obj[prop] === 'object') {
        const result = updateNode(obj[prop],value);
        if (result) return true;
      }
    }
  }
  return false;
}

changedNodes.forEach(({id,value}) => updateNode(myObject,id,value))
console.log(myObject);

,

以下方法使用两个函数的交替补充递归。

第一个操作任何给定对象树的下一个子级中的任何一项,并且确实触发第二个功能时,第二个功能要么寻找可能的项目更新,要么在可能的情况下可用的下一个子级,递归地传递回前一个,该子级将再次操作此对象级的任何项...依此类推...

function isObjectObject(type) {
  return (/^\[object\s+Object\]$/).test(
    Object.prototype.toString.call(type)
  );
}

function updateObjectItemViaBoundConfig(key) {
  const { parentItem,updateList } = this; // `this` equals bound configuration.

  const currentItem = parentItem[key];
  const updateItem = updateList.find(item => (item.key === key));

  if (updateItem) {

    const { value } = updateItem;
    if (isObjectObject(value)) {

      parentItem[key] = Object.assign({},value);
    } else {
      parentItem[key] = value;
    }
  } else if (isObjectObject(currentItem)) {

    // ... altering,complementary recursion ...
    updateObjectEntriesRecursively(currentItem,updateList);
  }
}
function updateObjectEntriesRecursively(obj,entryUpdateList) {
  // ... altering,complementary recursion ...
  Object.keys(obj).forEach(updateObjectItemViaBoundConfig,{
    parentItem: obj,updateList: entryUpdateList
  });
}


const sampleObject = {
  root_node: {
    node_child: {
      grand_child: 'grand_child value',grand_child_1: 'grand_child_1 value'
    },node_child_1: 'node_child_1 value'
  }
};

const changedNodes = [{
  key: 'grand_child_1',value: 'new grand_child_1 value'
},{
  key: 'node_child_1',value: 'new node_child_1 value'
}];


updateObjectEntriesRecursively(sampleObject,changedNodes);


console.log('sampleObject :',sampleObject);
.as-console-wrapper { min-height: 100%!important; top: 0; }

万一需要更新大量的对象节点,则下一次代码迭代会引入一个附加参数,该参数标记是否要/需要更改包含更新项的数组。它是通过从其数组中拼接当前更新的数据来实现的。因此,由于列表不断缩小,每次缩小时,都可以更快地对其进行搜索,因此它可能会获得一点点性能。

function isObjectObject(type) {
  return (/^\[object\s+Object\]$/).test(
    Object.prototype.toString.call(type)
  );
}

function updateObjectItemViaBoundConfig(key) {
  // `this` equals the bound config object.
  const { parentItem,updateList,isMutateList } = this;

  const currentItem = parentItem[key];
  const updateIndex = updateList.findIndex(item => (item.key === key));

  if (updateIndex >= 0) {
    const updateItem = updateList[updateIndex];
    const { value } = updateItem;

    if (isMutateList) {
      updateList.splice(updateIndex,1);
    }
    if (isObjectObject(value)) {

      parentItem[key] = Object.assign({},isMutateList);
  }
}
function updateObjectEntriesRecursively(obj,isMutateList) {
  // ... altering,isMutateList
  });
}


const sampleObject = {
  root_node: {
    node_child: {
      grand_child: 'grand_child value',value: 'new node_child_1 value'
}];


// updateObjectEntriesRecursively(sampleObject,changedNodes);
updateObjectEntriesRecursively(sampleObject,changedNodes,true);
// updateObjectEntriesRecursively(sampleObject,Array.from(changedNodes),true);

console.log('sampleObject :',sampleObject);
console.log('changedNodes :',changedNodes);
.as-console-wrapper { min-height: 100%!important; top: 0; }

,

我发现将对象遍历代码与实际更改值的代码分开是更简单的。因此,使用两个简单的函数,我们可以写得很不错:

const transform = (fn) => (obj) =>
  Object .fromEntries (Object .entries (obj) 
    .map (([k,v]) => Object(v) === v ? [k,transform (fn) (v)] : [k,fn (k,v)])
  )
    
const mapIds = (changed) => { 
  const keys = new Map (changed .map (({id,value}) => [id,value]))
  return (k,v) => keys .has (k) ? keys .get (k) : v
}

const changedNodes = [{id: 'grand_child_1',value: 'new node_child_1 value'}]
const objToBeUpdated = {root_node: {node_child: {grand_child: 'grand_child value',node_child_1: 'node_child_1 value'}}

console .log (
  transform (mapIds (changedNodes)) (objToBeUpdated)
)

  • transform 递归地遍历对象,并通过调用提供给它的函数来更新叶键值对。

    例如,

    const upperCase = (k,v) => 
      typeof v == 'string' ? v .toUpperCase () : v
    
    transform (upperCase) (objToBeUpdated)  //=>
    // {
    //     root_node: {
    //         node_child: {
    //             grand_child: "GRAND_CHILD VALUE",//             grand_child_1: "GRAND_CHILD_1 VALUE"
    //         },//         node_child_1: "NODE_CHILD_1 VALUE"
    //     }
    // }
    
  • mapIds 获取一个{id,value}对象的列表,并返回一个带有键和值的函数,并从该列表中返回匹配的值(如果是)否则返回原始值。

    例如:

    mapIds (changedNodes) ('grand_child_1','grand_child_1 value') //=> 'new grand_child_1 value'
    mapIds (changedNodes) ('foo','bar') //=> 'bar'
    

通过将changedNodes传递到mapIds并将结果函数传递到transform来组合它们,我们得到一个函数,它将接收您的对象并将这些更改应用于您的对象。

如果愿意,我们还可以使用它们编写自定义函数:

const updateObj = (changedNodes,obj) =>
  transform (mapIds (changedNodes)) (obj)

updateObj (changedNodes,objToBeUpdated) //=>
// {
//     root_node: {
//         node_child: {
//             grand_child: "grand_child value",//             grand_child_1: "new grand_child_1 value"
//         },//         node_child_1: "new node_child_1 value"
//     }
// }

这个想法可能有很多扩展。在这里,我们仅变换叶节点,但要返回一个[key,value]对并变换我们需要的任何节点并不难。在此版本的transform中,我们也不处理数组。添加它们将很简单。还有一个参数将transform的回调函数分为两个:一个谓词,用于确定是否需要转换节点;另一个,用于进行实际转换。同样,它应该很简单。但这足以解决眼前的问题。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。