【问题标题】:JS Array of Objects - Remove Duplicates and Merge nested objectsJS 对象数组 - 删除重复项并合并嵌套对象
【发布时间】:2019-12-26 23:08:10
【问题描述】:

仍在学习 JS,我一直在努力解决这个问题,并在 Stack Overflow link 上找到了一个类似的解决方案,我试图在此之后为我的解决方案建模,但我似乎无法弄清楚它是否适合我的使用案例..因此,我将不胜感激有关如何修复我拥有的代码的建议,甚至只是在正确方向上提供一些帮助

所以我试图从基于“objectID”的对象数组(见下文)中删除重复项,如果它们是唯一的,则将每个级别(lvl0、lvl1、lvl2)的嵌套“hierarchicalCategories”合并到一个数组中。

let objArray = [
  {
  "objectID": "1234",
  "hierarchicalCategories": {
    "lvl0": "Women's"

  }
},
{
  "objectID": "1234",
  "hierarchicalCategories": {
    "lvl0": "Women's",
    "lvl1": "Women's > Jewelry",
    "lvl2": "Women's > Jewelry > New"
  }
},
 {
  "objectID": "1234",
  "hierarchicalCategories": {
    "lvl0": "New",
    "lvl1": "New > Jewelry"
  }
},
{
  "objectID": "5678",
  "hierarchicalCategories": {
    "lvl0": "Men's",
    "lvl1": "Men's > Shoes",

  }
},
{
  "objectID": "5678",
  "hierarchicalCategories": {
    "lvl0": "New",
    "lvl1": "New > Shoes"
  }
}
]

预期结果应该是这样的:每个“objectID”都有一个实例,然后如果每个级别都有唯一值,则合并“hierarchicalCategories”

let newArray = [
 {
  "objectID": "1234",
  "hierarchicalCategories": {
    "lvl0": ["Women's","New"],
    "lvl1": ["Women's > Jewelry","New > Jewelry"],
    "lvl2": ["Women's > Jewelry > New"]
  }
},
{
  "objectID": "5678",
  "hierarchicalCategories": {
    "lvl0": ["Men's", "New"],
    "lvl1": ["Men's > Shoes","New > Shoes"]
  }
}
]

这是我使用的代码,它在一定程度上有效,但不能完全有效。基本上在每个级别(lvl0,lvl1,lvl2),我正在创建一个数组,然后仅在之前未包含它的情况下推送。但是,如果没有像"objectID": "5678" 中定义的级别,并且在任何重复项中都没有定义"lvl2",那么该插槽中的过滤数组中将有一个空数组,但似乎无法在不完全破坏它的情况下修复它。还可以接受其他建议+学习以改进代码或其他方法。

const filteredArr = objArray.reduce((acc, current) => {
    const x = acc.find(item => item.objectID === current.objectID);
    if (!x) {

        current.hierarchicalCategories.lvl0 ? current.hierarchicalCategories.lvl0 = [current.hierarchicalCategories.lvl0] : current.hierarchicalCategories.lvl0 = []
        current.hierarchicalCategories.lvl1 ? current.hierarchicalCategories.lvl1 = [current.hierarchicalCategories.lvl1] : current.hierarchicalCategories.lvl1 = []
        current.hierarchicalCategories.lvl2 ? current.hierarchicalCategories.lvl2 = [current.hierarchicalCategories.lvl2] : current.hierarchicalCategories.lvl2 = []

        acc.push(current)

    } else {

        if (current.hierarchicalCategories.lvl0 && !x.hierarchicalCategories.lvl0.includes(current.hierarchicalCategories.lvl0)) {
            x.hierarchicalCategories.lvl0.push(current.hierarchicalCategories.lvl0)
        }
        if (current.hierarchicalCategories.lvl1 && !x.hierarchicalCategories.lvl1.includes(current.hierarchicalCategories.lvl1)) {
            x.hierarchicalCategories.lvl1.push(current.hierarchicalCategories.lvl1)
        }
        if (current.hierarchicalCategories.lvl2 && !x.hierarchicalCategories.lvl2.includes(current.hierarchicalCategories.lvl2)) {
            x.hierarchicalCategories.lvl2.push(current.hierarchicalCategories.lvl2)
        }

    }
    return acc;
}, []);

我得到了这个回应,因为你可以在 lvl2 看到空数组

[
 {
  "objectID": "1234",
  "hierarchicalCategories": {
    "lvl0": ["Women's","New"],
    "lvl1": ["Women's > Jewelry","New > Jewelry"],
    "lvl2": ["Women's > Jewelry > New"]
  }
},
{
  "objectID": "5678",
  "hierarchicalCategories": {
    "lvl0": ["Men's", "New"],
    "lvl1": ["Men's > Shoes","New > Shoes"],
    "lvl2": []
  }
}
]

感谢任何愿意提供帮助的人!

【问题讨论】:

    标签: javascript arrays


    【解决方案1】:

    有几种方法可以做到这一点,但我认为使用 MapSet 可以帮助对事物进行分组并使其独一无二:

    let objArray = [{"objectID": "1234","hierarchicalCategories": {"lvl0": "Women's"}},{"objectID": "1234","hierarchicalCategories": {"lvl0": "Women's","lvl1": "Women's > Jewelry","lvl2": "Women's > Jewelry > New"}},{"objectID": "1234","hierarchicalCategories": {"lvl0": "New","lvl1": "New > Jewelry"}},{"objectID": "5678","hierarchicalCategories": {"lvl0": "Men's","lvl1": "Men's > Shoes",}},{"objectID": "5678","hierarchicalCategories": {"lvl0": "New","lvl1": "New > Shoes"}}];
    
    let map = new Map(objArray.map(o => [o.objectID, {}] ));
    for (let obj of objArray) {
        let cats = map.get(obj.objectID);
        for (let [key, val] of Object.entries(obj.hierarchicalCategories)) {
            cats[key] = (cats[key] || new Set).add(val);
        }
    }
    let result = Array.from(map.entries(), ([objectId, cats]) => ({ 
        objectId, 
        hierarchicalCategories: Object.fromEntries(Object.entries(cats).map(([k, v]) => 
            [k, [...v]]
        )) 
    }));
    
    console.log(result);

    说明

    首先创建一个 Map,以 objectID 为键,并将相应的值初始化为空对象。 Map 构造函数获取 [key, value] 对的列表,它将从中创建 Map。它不会抱怨提供给它的重复键。

    然后对于输入数组中的每个对象,从映射中检索相应的对象并将其分配给cats。第一次它将是一个空对象。然后将输入对象中的hierarchicalCategories 添加到cats。在执行此操作时,将验证密钥(如“lvl2”)是否已存在于cats 中。如果不是,则 cats[key] 未定义,只有这样 || 运算符才会计算正确的操作数,因此会创建一个 Set。否则我们知道它已经是一个集合。我们将值(如“Woman's”)添加到该 Set。使用 Set 的好处是可以忽略重复项。

    这就是第一个for 循环的作用。它本质上将输入转换为一个结构,该结构将以有效的方式处理分组和重复。

    那么代码的最后一部分会将这些信息转化为想要的输出结构。

    map.entries 将给出它拥有的键/值组合。现在值部分不再是 empty 对象,因为我们在之前的循环中向它们添加了数据。那些cats 对象可能有多个“lvl”键和关联的集合。

    Array.from 将允许我们迭代那些map.entries() 并在映射器回调函数中对每一个做一些事情。该回调函数为每个条目返回一个对象。它用括号括起来,以避免 JS 解析器将大括号误解为代码块(实际上它会抱怨它)。

    使用Object.entries,我们查找每个集合,并将它们映射到标准数组,使用[...v]Object.fromEntries 将其组合回一个对象(它与 `Object.entries 相反)。

    【讨论】:

    • 这太棒了,效果很好,但是您介意更详细地解释一下这是如何工作的吗?试着多了解一点,这样我就可以继续学习
    • @trincot 你的实现在我的编辑器中不起作用。但是要理解这个解决方案,我们需要知道新类型的对象Map & Set; read the doc
    • @MuhammadAmirozzamanNiaz,在 2019 年底,随着去年 6 月发布的 EcmaScript2019,我们真的不能说 MapSet 是新的。它们从 EcmaScript2015 开始可用。这更多地表明你的编辑跟不上过去四年的发展。
    • @trincot 第一次对任何人来说总是新伙伴。你说得对,那些不是新的。我想你明白我的意思了。我的 nodejs 版本是 10.16.3
    【解决方案2】:

    我喜欢简单。

    const myArray = [{},{}];
    const myArrayMirror = [...myArray];
    
    const sortedArray = myArray.filter(originalObj, originalIndex => {
        let objIsDuplicate = false;
        myArrayMirror.forEach(duplicate, duplicateIndex => {
            if (duplicateIndex > originalIndex && originalObj === duplicate) {
                objIsDuplicate = true;
            }
        });
        return objIsDuplicate;
    });
    

    【讨论】:

      【解决方案3】:

      我会这样做:

      const objArray = [YOUR_OBJECTS_LIVE_HERE]
      
      const mapWithUniqueObjs = new Map()
      
      // loop over properties to set it to your map
      for (let i = 0; i < objArray.length; i++) {
        // Map's keys are always unique, so in case
        // we already have this item in the map, it will be overwritten
        uniqueObj.set(objArray[i].objectID, objArray[i])
      }
      
      // and make it an array
      const arrWithUniqueObjs = [...mapWithUniqueObjs]
      
      

      【讨论】:

        【解决方案4】:

        将其附加到代码的末尾(分配过滤数组的位置):

        .map(function(x) {
            if (x.hierarchicalCategories.lvl2.length === 0) {
              delete x.hierarchicalCategories.lvl2
            }
            if (x.hierarchicalCategories.lvl1.length === 0) {
              delete x.hierarchicalCategories.lvl1
            }
             if (x.hierarchicalCategories.lvl0.length === 0) {
              delete x.hierarchicalCategories.lvl0
            }
            return x
        })
        

        let objArray = [
          {
          "objectID": "1234",
          "hierarchicalCategories": {
            "lvl0": "Women's"
        
          }
        },
        {
          "objectID": "1234",
          "hierarchicalCategories": {
            "lvl0": "Women's",
            "lvl1": "Women's > Jewelry",
            "lvl2": "Women's > Jewelry > New"
          }
        },
         {
          "objectID": "1234",
          "hierarchicalCategories": {
            "lvl0": "New",
            "lvl1": "New > Jewelry"
          }
        },
        {
          "objectID": "5678",
          "hierarchicalCategories": {
            "lvl0": "Men's",
            "lvl1": "Men's > Shoes",
        
          }
        },
        {
          "objectID": "5678",
          "hierarchicalCategories": {
            "lvl0": "New",
            "lvl1": "New > Shoes"
          }
        }
        ]
        
        const filteredArr = objArray.reduce((acc, current) => {
            const x = acc.find(item => item.objectID === current.objectID);
            if (!x) {
        
                current.hierarchicalCategories.lvl0 ? current.hierarchicalCategories.lvl0 = [current.hierarchicalCategories.lvl0] : current.hierarchicalCategories.lvl0 = []
                current.hierarchicalCategories.lvl1 ? current.hierarchicalCategories.lvl1 = [current.hierarchicalCategories.lvl1] : current.hierarchicalCategories.lvl1 = []
                current.hierarchicalCategories.lvl2 ? current.hierarchicalCategories.lvl2 = [current.hierarchicalCategories.lvl2] : current.hierarchicalCategories.lvl2 = []
        
                acc.push(current)
        
            } else {
        
                if (current.hierarchicalCategories.lvl0 && !x.hierarchicalCategories.lvl0.includes(current.hierarchicalCategories.lvl0)) {
                    x.hierarchicalCategories.lvl0.push(current.hierarchicalCategories.lvl0)
                }
                if (current.hierarchicalCategories.lvl1 && !x.hierarchicalCategories.lvl1.includes(current.hierarchicalCategories.lvl1)) {
                    x.hierarchicalCategories.lvl1.push(current.hierarchicalCategories.lvl1)
                }
                if (current.hierarchicalCategories.lvl2 && !x.hierarchicalCategories.lvl2.includes(current.hierarchicalCategories.lvl2)) {
                    x.hierarchicalCategories.lvl2.push(current.hierarchicalCategories.lvl2)
                }
        
            }
            return acc;
        }, []).map(function(x) {
            if (x.hierarchicalCategories.lvl2.length === 0) {
              delete x.hierarchicalCategories.lvl2
            }
            if (x.hierarchicalCategories.lvl1.length === 0) {
              delete x.hierarchicalCategories.lvl1
            }
             if (x.hierarchicalCategories.lvl0.length === 0) {
              delete x.hierarchicalCategories.lvl0
            }
            return x
        })
        console.log(filteredArr);

        【讨论】:

          【解决方案5】:

          这似乎是一个两步的过程:

          1. groupBy objectID:

          例如变换[{objectID: 1, ...}, {objectID: 1, ...}, {objectID: 2, ...}]

          转入[ [{objectID: 1, ...}, {objectID: 1, ...}], [{objectID: 2, ...}] ]

          1. reduce (/deepmerge)各组:

          例如[{objectID: 1, ...merged props}, {objectID: 2, ...merged props}]

          this is an example 使用方便的groupBy lodashdeepmerge

          deepmerge 是一个非常流行的库,用于合并带有/不带有数组/嵌套数组的复杂嵌套对象

          【讨论】:

          • 我会查看其中的一些库,感谢您的洞察
          猜你喜欢
          • 2019-11-15
          • 1970-01-01
          • 1970-01-01
          • 2017-06-19
          • 1970-01-01
          • 2021-01-23
          • 2020-04-15
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多