1

I am trying to boil some data down using Object.keys() and .map(). I want to get back an array of objects that contain arrays. The .map() returns the correct objects (currently two) in the array, but they are duplicated by the number of loops in the .map(). Below is a simplified example of what I am trying to do

// The data:
const theObjects = {
    object1: ['a', 'b', 'c'],
    object2: ['d', 'e', 'f'],
    object3: ['g', 'h', 'i'],
    object4: ['j', 'k', 'l'],
    object5: ['m', 'n', 'o'],
};

// Set up the keys for the object I want to get back. It looks weird here, but it is an emulation of data that is extracted from the source of my real dataset:
let season;
const seasons = [
    '2021-2022',
    '2021-2022',
    '2021-2022',
    '2022-2023',
    '2022-2023',
];

//Set up the return object. Extract the keys from the dataset object and map over them
var compiler = {};
const returnedData = Object.keys(theObjects).map((key, index) => {

    // Set up `seasons` to become the object keys of final output and initialize the objects as needed.
    var season = seasons[index];

    Array.isArray(compiler[season]) ? compiler : (compiler[season] = []);

    // `.push()` the current data into the appropriate array:
    compiler[season].push(theObjects[key]);

    // Test 1: Test the current state of the compiler object and `return` the object:
    console.log('COMPILER[', key, ']', index, ': ', compiler);
    return compiler;
});

//Test 2: The final object returned
console.log('Returned: ', returnedData);

Test 1 logs once for each loop in the .map() and we can watch the final object build:

COMPILER[ object1 ] 0 :  { '2021-2022': [ [ 'a', 'b', 'c' ] ] }
COMPILER[ object2 ] 1 :  { '2021-2022': [ [ 'a', 'b', 'c' ], [ 'd', 'e', 'f' ] ] }
COMPILER[ object3 ] 2 :  {
  '2021-2022': [ [ 'a', 'b', 'c' ], [ 'd', 'e', 'f' ], [ 'g', 'h', 'i' ] ]
}
COMPILER[ object4 ] 3 :  {
  '2021-2022': [ [ 'a', 'b', 'c' ], [ 'd', 'e', 'f' ], [ 'g', 'h', 'i' ] ],
  '2022-2023': [ [ 'j', 'k', 'l' ] ]
}
COMPILER[ object5 ] 4 :  {
  '2021-2022': [ [ 'a', 'b', 'c' ], [ 'd', 'e', 'f' ], [ 'g', 'h', 'i' ] ],
  '2022-2023': [ [ 'j', 'k', 'l' ], [ 'm', 'n', 'o' ] ]
}

Test 2 logs the final object. The data matches the object returned in the last loop of the .map(), but it is repeated 5 times:

Returned: [
    {
        '2021-2022': [
            ['a', 'b', 'c'],
            ['d', 'e', 'f'],
            ['g', 'h', 'i'],
        ],
        '2022-2023': [
            ['j', 'k', 'l'],
            ['m', 'n', 'o'],
        ],
    },
    {
        '2021-2022': [
            ['a', 'b', 'c'],
            ['d', 'e', 'f'],
            ['g', 'h', 'i'],
        ],
        '2022-2023': [
            ['j', 'k', 'l'],
            ['m', 'n', 'o'],
        ],
    },
    {
        '2021-2022': [
            ['a', 'b', 'c'],
            ['d', 'e', 'f'],
            ['g', 'h', 'i'],
        ],
        '2022-2023': [
            ['j', 'k', 'l'],
            ['m', 'n', 'o'],
        ],
    },
    {
        '2021-2022': [
            ['a', 'b', 'c'],
            ['d', 'e', 'f'],
            ['g', 'h', 'i'],
        ],
        '2022-2023': [
            ['j', 'k', 'l'],
            ['m', 'n', 'o'],
        ],
    },
    {
        '2021-2022': [
            ['a', 'b', 'c'],
            ['d', 'e', 'f'],
            ['g', 'h', 'i'],
        ],
        '2022-2023': [
            ['j', 'k', 'l'],
            ['m', 'n', 'o'],
        ],
    },
];

I can make this work by simply using returnedData[0] and calling it a day. Unfortunately, my anal-retentive side just can't let this go. Is there something that I can do to just get back something that looks like the COMPILER[ object5 ]... output?

Tim R
  • 43
  • 7

3 Answers3

2

but it is repeated 5 times:

So you used .map, it always returns an array of the original size, this is the natural behavior of .map. If you need to resize an array or make an object, you need to use .reduce.

The second is that you use var compiler = {};, a global variable, and return it every time inside .map return compiler;. So you're always returning the same object - that's the problem.

Anyway, in this case it's better to use .reduce, for example:

const theObjects = {object1: ['a', 'b', 'c'],object2: ['d', 'e', 'f'],object3: ['g', 'h', 'i'],object4: ['j', 'k', 'l'],object5: ['m', 'n', 'o']};
const seasons = ['2021-2022','2021-2022','2021-2022','2022-2023','2022-2023'];

const returnedData = Object.values(theObjects)
  .reduce((acc, values, index) => {
    const seson = seasons[index];
    acc[seson] ??= [];
    acc[seson].push(values);
    return acc;
  }, {});
  
// Or the same result but in "modern" non-mutable-way  
const returnedData1 = Object.values(theObjects)
  .reduce((acc, values, index) => ({
    ...acc, 
    [seasons[index]]: [...(acc[[seasons[index]]] || []), values]
  }), {});

console.log('Returned: ', returnedData1);
.as-console-wrapper { max-height: 100% !important; top: 0 }
A1exandr Belan
  • 4,442
  • 3
  • 26
  • 48
  • Yeah, I moved the `var compiler` inside and outside of the function a lot while I was playing with this. Also, in my playground it was `let` rather than `var`. I'm not sure how I managed to change it. Both of the variations worked in my playground, so thanks for the input. I think I'm going with the first version though, cuz I don't have a clue what the second one is doing :/ – Tim R Aug 24 '22 at 18:08
  • I used this method in my real code base and it worked great. Thanks @A1exandr – Tim R Aug 27 '22 at 02:49
1

Here is what it seems to me that you need. This avoids creating duplicate data. For the reasoning why your current code doesn't work I really don't have an explanation, I was actually quite baffled after seeing that the data somehow got fully looped through and set up on the first iteration. But if you find an answer why please share it in your post.

let compiledData = {};
let keys = Object.keys(theObjects);
let length = seasons.length;
for(let i = 0; i < length; i++) {
    if(!compiledData[seasons[i]]) compiledData[seasons[i]] = [];
    compiledData[seasons[i]].push(theObjects[keys[i]]);
} 
console.log(compiledData);
restart it
  • 96
  • 6
  • This worked for me. Thanks for the suggestion. As for the duplicated return from `.map`, I was pretty confused by it. There is a caveat in MDN [link](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) about using `forEach` or `for...of` instead of `.map()` in certain cases. It might have something to do with that. – Tim R Aug 24 '22 at 18:04
  • . . . On the other hand, @A1exandrBelan makes a good point about the array `.map()` returns as opposed to `.reduce()`. – Tim R Aug 24 '22 at 18:06
0

I didn't read your whole thing because that's a lot. But if you want to dedupe an array (the result of map), use a set...

const arr = ["a", "a", "b", "b", "c", "c", "c", "c"];
const res = [...new Set(arr)];
console.log(res);  
// ["a","b","c"]

Also, checkout this other post for more info: Remove Duplicates within a map function

Travis Heeter
  • 13,002
  • 13
  • 87
  • 129
  • I know about the deduping, I'm more curious to find out what I'm doing that I am getting the duplicates in the first place – Tim R Aug 22 '22 at 20:14