【问题标题】:Flatten nested object/array in javascript在javascript中展平嵌套对象/数组
【发布时间】:2020-09-21 07:49:31
【问题描述】:

我是 Javascript 新手,我有想要展平的嵌套对象和数组。

我有……

[{ a: 2, b: [{ c: 3, d: [{e: 4, f: 5}, {e: 5,f: 6}]}, 
             { c: 4, d: [{e: 7, f: 8}]}
            ]
}]

并且想要...

[{a:2,c:3,e:4,f:5}, {a:2,c:3,e:5,f:6}, {a:2,c:4,e:7,f:8}]

我尝试为我的数组中的对象调整以下函数,但我只得到数组中的最终对象 [{a:2,c:4,e:7,f:8}] @987654321 @。我认为我的问题是知道如何遍历数组和对象?

原始脚本:

function flatten(obj) {
    var flattenedObj = {};
    Object.keys(obj).forEach(function(key){
        if (typeof obj[key] === 'object') {
            $.extend(flattenedObj, flatten(obj[key]));
        } else {
            flattenedObj[key] = obj[key];
        }
    });
    return flattenedObj;    
}

我的脚本(两者的结果相同):

flat_array=[];
function superflat(array){
   for (var i = 0; i < array.length; i++) {
       var obj = array[i]
       var flattenedObj = {};
       Object.keys(obj).forEach(function(key){
          if (typeof obj[key] === 'object') {
             $.extend(flattenedObj, flatten(obj[key]));
          } else {
             flattenedObj[key] = obj[key];
          }
       });
       flat_array.push(flattenedObj);
    }
};
mega_flat_array=[];
function megaflatten(obj) {
     Object.keys(obj).forEach(function(key){
     var flattenedObj = {};
        if (typeof obj[key] === 'object') {
            $.extend(flattenedObj, flatten(obj[key]));
        } else {
            flattenedObj[key] = obj[key];
        }
     mega_flat_array.push(flattenedObj);
     });
}

感谢您的帮助

【问题讨论】:

  • 一个对象中不能有两个相同的属性名。
  • @Rebeca Fiadeiro 您期望的输出应该是一个数组;加上你输入的 JSON 是错误的
  • 请通过console.log查看您的数据。你不能有两个同名的属性。
  • 再次感谢您的反馈。您是正确的,它不是 JSON 而是数组。抱歉,我再次编辑了问题

标签: javascript arrays object nested


【解决方案1】:

它有点像庞然大物,它不会保留键的顺序,但它确实可以在没有重复的情况下工作。

它是递归的,所以要注意调用堆栈。

  • 首先,遍历数组中的项目,
  • 如果项目是数组,则进行递归调用。
    • 从该调用返回时,如果返回的对象数量多于最终结果中的当前数量,则使用最终结果中对象的属性更新返回的对象,注意避免覆盖预先存在的属性。
    • 否则使用返回结果中的属性更新最终结果,再次注意不要覆盖现有属性。
  • 如果项目不是数组
    • 如果这是第一项,则将其放入最终结果中
    • 否则将该项的属性添加到最终结果中的所有项中,而不覆盖任何项。

function makeFlat(arr) //assume you're always passing in an array
{
  let objects = [];
  arr.forEach(item =>
  {
    let currentObject = {};
    const keys = Object.keys(item);
    keys.forEach(key =>
    {
      const obj = item[key];
      if(Array.isArray(obj))
      {
        let parts = makeFlat(obj);
        if(objects.length > 0)
        {          
          if(parts.length > objects.length)
          {
            parts.forEach(part =>
            {
              objects.forEach(ob =>
              {
                Object.keys(ob).forEach(k =>
                {
                  if(Object.keys(part).indexOf(k) == -1)
                  {
                    part[k] = ob[k];
                  }
                });
              });
            });
            objects = parts;
          }
          else
          {
            objects.forEach(ob =>
            {
              parts.forEach(part =>
              {
                Object.keys(part).forEach(k =>
                {
                  if(Object.keys(ob).indexOf(k) == -1)
                  {
                    ob[k] = part[k];
                  }
                });
              });
            });
          }
        }
        else
        {
          objects = parts;
        }
      }
      else
      {
        if(Object.keys(currentObject).length == 0)
        {
          objects.push(currentObject);
        }
        currentObject[key] = item[key];
        
        objects.forEach(ob =>
        {
          if(Object.keys(ob).indexOf(key) == -1)
          {
            ob[key] = currentObject[key]
          }
        });
      }
    });
  });
  return objects;
}

const inp = [{ a: 2, b: [{ c: 3, d: [{e: 4, f: 5}, {e: 5,f: 6}]}, 
                         { c: 4, d: [{e: 7, f: 8}]}
            ], g:9
}];

let flattened = makeFlat(inp);

flattened.forEach(item => console.log(JSON.stringify(item)));

【讨论】:

    【解决方案2】:

    我建议从更简单的数据对象开始测试您的函数,然后逐步添加更复杂的对象,直到您的函数按预期执行。对于每个

    在这里,您可以看到我是如何从一个简单的 test1 对象开始,然后是 test2 等等,以便将逻辑分解为更小的增量。

    要删除我们之前的重复项,我不得不 throwcatch 一个错误来打破递归 forEach 循环,这增加了不必要的重复“行” - 也许最好使用正常的for 循环,您可以简单地循环其中break;,然后对真正的错误使用错误处理。

    递归函数的基本思想是检查对象的类型(数组或对象),然后循环遍历它们以添加值,但是需要对它们进行另一次检查以查看它们是否不是数组而不是对象,如果是,请再次调用该函数。当发现重复键即 { c: 3} 时,删除当前键并添加新键,然后继续循环。

    如果您有更多示例数据,您可以添加更多测试,但有更好的库可以帮助您进行 TDD(测试驱动开发)。

    
    
    const isArray = (arr) => {
      return Array.isArray(arr);
    };
    
    const isObject = (obj) => {
      return typeof obj === "object" && obj !== null;
    };
    
    const flatten = (tree, row, result) => {
      try {
        
        if (isArray(tree)) {
          tree.forEach((branch, index) => {
            flatten(branch, row, result);
          });
        } else if (isObject(tree)) {
          Object.keys(tree).forEach((key) => {
            //we don't want to add objects or arrays to the row - 
            if (!isArray(tree[key]) && !isObject(tree[key])) {
              
              if (key in row) {
                
                // new row detected, get existing keys to work with
                let keysArray = Object.keys(row);
                // we are going to loop backwards and delete duplicate keys
                let end = Object.keys(row).length;
                let stopAt = Object.keys(row).indexOf(key);
                //delete object keys from back of object to the newly found one
                for (let z = end; z > stopAt; z--) {
                  delete row[keysArray[z - 1]];
                }
    
                row[key] = tree[key];
              } else {
                row[key] = tree[key];
              }
            } else {
              flatten(tree[key], row, result);
              throw "skip";
            }
          });
    
          //all other rows in results will be overridden if we don't stringify
          result.push(JSON.stringify(row));
          
        }
      } catch (e) {
        //console.log(e)
      } finally {
        return result.map((row) => JSON.parse(row));
      }
    };
    
    
    ///tests
    const test1 = [
        {
          a: 2,
          b: 3,
        },
      ];
      const expected1 = [{ a: 2, b: 3 }];
      
      const test2 = [
        {
          a: 2,
          b: [
            {
              c: 3,
            },
          ],
        },
      ];
      const expected2 = [{ a: 2, c: 3 }];
      
      const test3 = [
        {
          a: 2,
          b: [
            {
              c: 3,
            },
            { c: 4 },
            { c: 5 },
          ],
        },
      ];
      const expected3 = [
        { a: 2, c: 3 },
        { a: 2, c: 4 },
        { a: 2, c: 5 },
      ];
      
      let test4 = [
        {
          a: 2,
          b: [
            {
              c: 3,
              d: [
                { e: 4, f: 5 },
                { e: 5, f: 6 },
              ],
            },
            { c: 4, d: [{ e: 7, f: 8 }] },
          ],
        },
      ];
      const expected4 = [
        { a: 2, c: 3, e: 4, f: 5 },
        { a: 2, c: 3, e: 5, f: 6 },
        { a: 2, c: 4, e: 7, f: 8 },
      ];
    const test = (name, res, expected) => {
      console.log(
        `${name} passed ${JSON.stringify(res) === JSON.stringify(expected)}`
      );
      //console.log(res, expected);
    };
    
    //test("test1", flatten(test1, {}, []), expected1);
    //test("test2", flatten(test2, {}, []), expected2);
    //test("test3", flatten(test3, {}, []), expected3);
    test("test4", flatten(test4, {}, []), expected4);
    
    

    【讨论】:

    • 感谢@Herald Smit!输出肯定是我想要的格式 - 只需要更仔细地查看重复部分,因为我期望的“行”比我运行数组时得到的要多。
    • 嗨@RebecaFiadeiro,我做了一些更改以消除重复项,并添加了测试,因此您可以轻松测试更多数据样本。祝你好运!
    猜你喜欢
    • 2019-07-20
    • 2020-12-22
    • 2020-07-11
    • 2013-11-06
    • 2021-03-22
    • 2020-06-23
    • 1970-01-01
    • 2018-04-20
    • 1970-01-01
    相关资源
    最近更新 更多