Manipulating arrays to extract unique objects and count occurrences The data describes cycling activities (key1) and chunks of them (key2). In total there are around 1,000 key1 objects and arrays under key1 are 1-100 long.


Is there a simpler, more semantic, faster, or otherwise better way than to use two nested map in step 1?
I realise that step 3 is longer than it should be and there has to be a better way to do it. How can step 3 be improved?




// Original array
const arr = [
    {key1: [{key2: {id: 1, name: 'a'}}]},
    {key1: [{key2: {id: 2, name: 'b'}}]},
    {key1: [{key2: {id: 2, name: 'b'}}, {key2: {id: 3, name: 'c'}}]}
];
console.log(arr);

// Step 1: Extract meaningful information from original array
const arrOfArrOfObj = arr
    .map(category =&gt; category.key1
        .map(subCategory =&gt; (
                {
                    id: subCategory.key2.id,
                    name: subCategory.key2.name
                }
            )
        )
    );
console.log(arrOfArrOfObj);

// Step 2: Make the array one dimensional
const arrOfObj = [].concat(...arrOfArrOfObj);
console.log(arrOfObj);

// Step 3: Remove duplicates and count object occurrences.
let dedupedArrWithCount = [];
l = arrOfObj.length;
for (let i = 0; i &lt; l; i++) {
    let objExists = false;
    for (let j = 0; j &lt; dedupedArrWithCount.length; j++) {
        // Two objects are identical if their ids are identical.
        if (arrOfObj[i].id === dedupedArrWithCount[j].id) {
            objExists = true;
            dedupedArrWithCount[j].count += 1;
        }
    }
    if (!objExists) {
        dedupedArrWithCount.push({
            id: arrOfObj[i].id,
            name: arrOfObj[i].name,
            count: 1
        })
    }
}

console.log(dedupedArrWithCount);
Responding to your questions


  “Is there a simpler, more semantic, faster, or otherwise better way than to use two nested map in step 1?”


While I like the benefits of functional approaches, they are often slower because of the extra function calls. 

Steps 1 and 2 could be simplified by using two for...of loops (since ecmascript-6 features like const are also used). Also, instead of constructing a new object to return in the nested map callback, you can just return subCategory.key2 (though perhaps you simplified the data and the original data contains more properties that aren't needed in the end).

const arrOfObj = [];
for (let category of arr) {
    for (let subCategory of category.key1) {
        arrOfObj.push(subCategory.key2);
    }
}


This generally works faster, at least for the small dataset supplied - see this jsPerf test for comparison


  “I realise that step 3 is longer than it should be and there has to be a better way to do it. How can step 3 be improved?”


I was thinking of using JSON.stringify() to make an array of counts in step 1 and then use JSON.parse() in step 3 for lookups in constant time, but apparently that was slower, possibly because of the original data set which only has 1 collision. Maybe for a larger dataset that would be faster. See this jsPerf for a comparison.



const arr = [
    {key1: [{key2: {id: 1, name: 'a'}}]},
    {key1: [{key2: {id: 2, name: 'b'}}]},
    {key1: [{key2: {id: 2, name: 'b'}}, {key2: {id: 3, name: 'c'}}]}
];
const counts = {};
for (let category of arr) {
  for (let subCategory of category.key1) {
    const countKey = JSON.stringify(subCategory.key2)
    counts[countKey] = (counts[countKey] || 0) + 1;
  }
}
const dedupedArrWithCount = [];
for (let key in counts) {
  const obj = JSON.parse(key);
  dedupedArrWithCount.push(Object.assign(obj, {
    count: counts[key]
  }));
}
console.log(dedupedArrWithCount);






Other feedback

The variable l is declared without any keyword: 


let dedupedArrWithCount = [];
l = arrOfObj.length;



Unless this code is wrapped in an IIFE, l becomes a global variable, which can lead to unintentional consequences if that name is used later. Read more about this topic in this post about global variables.  It is advisable to use const (or let if there was a need to re-assign it).  

In that same vein, dedupedArrWithCount could be declared with the const keyword, since it is never re-assigned, just mutated using the push method.



One other thing I noticed is that 


const arrOfObj = [].concat(...arrOfArrOfObj);



Is equivalent to

const flatArr = arrOfArrOfObj.flat();


Using .flat() appears to run faster in Firefox and Safari but not Chrome, at least when I ran this jsPerf.