Search code examples
javascriptarraysobjectdirectoryreduce

Using reduce, how to make an object directory where every key is a unique value and stores repeated values as sub-objects?


How do I create a function using reduce which will take in an array of objects and return an object directory with unique keys, but allowing for repeated values?

  1. each key is the name of the person
  2. each repeated key has a nested array with a directory of numbers
  3. if there are no repeated keys, the key should holds only one object

I would like the output to look like this:

{
  Bob: {
    '0': { name: 'Bob', age: 30, voted: true },
    '1': { name: 'Bob', age: 31, voted: true },
    '2': { name: 'Bob', age: 32, voted: true },
       },
  Jake: {
    '0': { name: 'Jake', age: 20, voted: false },
    '1': { name: 'Jake', age: 32, voted: true }
       },
  Phil: { name: 'Phil', age: 21, voted: true },
  Ed: { name: 'Ed', age: 55, voted: true },
  Tami: { name: 'Tami', age: 54, voted: true },
  Mary: { name: 'Mary', age: 31, voted: false },
  Becky: { name: 'Becky', age: 43, voted: false },
  Joey: { name: 'Joey', age: 41, voted: true },
  Jeff: { name: 'Jeff', age: 30, voted: true },
  Zack: { name: 'Zack', age: 19, voted: false }
}

This is my code, the results eat one another if two repeated names happen, and it spreads the first object into the key without adding a number key to it.

let count = 0
const toNamedObj = (arr) => {
  return arr.reduce((acc, cur) => {
    let key = Object.values(cur)[0]
    if(acc.hasOwnProperty(key)){
      count ++
      console.log(count)
      return {[key]: {...acc[key], [count]: cur} }
    }
    return {...acc, [key]: cur}
  } ,{})
}

const voters = [
  {name:'Bob' , age: 30, voted: true},
  {name:'Bob' , age: 31, voted: true},
  {name:'Bob' , age: 32, voted: true},
  {name:'Jake' , age: 32, voted: true},
  {name:'Kate' , age: 25, voted: false},
  {name:'Jake' , age: 20, voted: false},
  {name:'Phil' , age: 21, voted: true},
  {name:'Ed' , age:55, voted:true},
  {name:'Tami' , age: 54, voted:true},
  {name: 'Mary', age: 31, voted: false},
  {name: 'Becky', age: 43, voted: false},
  {name: 'Joey', age: 41, voted: true},
  {name: 'Jeff', age: 30, voted: true},
  {name: 'Zack', age: 19, voted: false}
];
console.log(toNamedObj(voters)); 


Solution

  • Presented below is one possible way to achieve the desired objective.

    Code Snippet

    // method to transform arr to desired format
    const toNamedObj = arr => (
      arr.reduce(
        (acc, {name, voted, ...rest}, idx) => (
          (acc[name] ??= []).push({ name, voted, ...rest }),
          (idx === arr.length - 1 && Object.entries(acc).forEach(
              ([k, v]) => (acc[k] = (v.length === 1) ? v[0] : {...v})
          )),
          acc
        ),
        {}
      )
    );
    /* with explanation
    // method to transform arr to desired format
    const toNamedObj = arr => (
      // iterate using ".reduce()"
      arr.reduce(
        (acc, {name, voted, ...rest}, idx) => {
          // if "name" not present in "acc", set it as empty array
          acc[name] ??= [];
          // push elt into array
          acc[name].push({ name, voted, ...rest });
          // when iterating over the last item, special processing
          if (idx === arr.length - 1) {    // processed last elt in arr
            // review each value in "acc" and convert to object
            Object.entries(acc)
            .forEach(([k, v]) => {
              if (v.length === 1) {       // only one elt in value-array
                // place the elt as-is
                acc[k] = v[0];
              } else {                    // multiple elts in val-arr
                // convert val-arr to object
                acc[k] = {...v};
              }
            });
          };
          // always return the accumulator "acc" in ".reduce()" callback
          return acc;
        },
        {}        // "acc" is set as an empty object initially
      )
    );
    */
    
    const voters = [
      {name:'Bob' , age: 30, voted: true},
      {name:'Bob' , age: 31, voted: true},
      {name:'Bob' , age: 32, voted: true},
      {name:'Jake' , age: 32, voted: true},
      {name:'Kate' , age: 25, voted: false},
      {name:'Jake' , age: 20, voted: false},
      {name:'Phil' , age: 21, voted: true},
      {name:'Ed' , age:55, voted:true},
      {name:'Tami' , age: 54, voted:true},
      {name: 'Mary', age: 31, voted: false},
      {name: 'Becky', age: 43, voted: false},
      {name: 'Joey', age: 41, voted: true},
      {name: 'Jeff', age: 30, voted: true},
      {name: 'Zack', age: 19, voted: false}
    ];
    console.log(toNamedObj(voters));
    .as-console-wrapper { max-height: 100% !important; top: 0 }

    Explanation

    Inline comments added to the snippet above.