Nguyễn Văn Phong
Nguyễn Văn Phong

Reputation: 14198

Javascript - Group by data from object arrays

I have the object arrays like below:

const obj = {
  top: [
    { id: 1, name: "Spider-man", rating: 1 },
    { id: 2, name: "Iron man", rating: 3 },
    { id: 3, name: "Hulk", rating: 5 }
  ],
  middle: [
    { id: 1, name: "Spider-man", rating: 4.5 },
    { id: 2, name: "Iron man", rating: 3.5 },
    { id: 3, name: "Hulk", rating: 1.5 }
  ],
  bottom: [
    { id: 1, name: "Spider-man", rating: 5 },
    { id: 2, name: "Iron man", rating: 2 },
    { id: 3, name: "Hulk", rating: 4 }
  ]
};

And what I want is to group by id, name and list out rating belonging its like below

const users = [
  {
    id: 1,
    name: "Spider-man",
    rating: {top: 1, middle: 4.5, bottom: 5}
  },
  {
    id: 2,
    name: "Iron man",
    rating: {top: 3, middle: 3.5, bottom: 2}
  },
  {
    id: 3,
    name: "Hulk",
    rating: {top: 5, middle: 1.5, bottom: 4}
  }
];

I've tried this approach but seems it can achieve in more ways such as .reduce, for...of with more elegant, right?

const obj = {
  top: [
    { id: 1, name: "Spider-man", rating: 1 },
    { id: 2, name: "Iron man", rating: 3 },
    { id: 3, name: "Hulk", rating: 5 }
  ],
  middle: [
    { id: 1, name: "Spider-man", rating: 4.5 },
    { id: 2, name: "Iron man", rating: 3.5 },
    { id: 3, name: "Hulk", rating: 1.5 }
  ],
  bottom: [
    { id: 1, name: "Spider-man", rating: 5 },
    { id: 2, name: "Iron man", rating: 2 },
    { id: 3, name: "Hulk", rating: 4 }
  ]
};

var result = obj.top.map(x => ({
  id: x.id,
  name: x.name,
  rating: {
    top: obj.top.find(t => t.id === x.id).rating,
    middle: obj.middle.find(t => t.id === x.id).rating,
    bottom: obj.bottom.find(t => t.id === x.id).rating,
  }
}));
                          
console.log(result);

Any other ways to achieve it? Thanks in advance.

Upvotes: 3

Views: 186

Answers (5)

Nguyễn Văn Phong
Nguyễn Văn Phong

Reputation: 14198

Using Dictionary along with Logical nullish assignment (??=)

The main idea includes 2 steps:

  1. Loop all [key, values] of the object.
  2. Inner each the values of the object, we loop to determine which the user's rating along with key belongs by user.id.

const obj = {
  top: [
    { id: 1, name: "Spider-man", rating: 1 },
    { id: 2, name: "Iron man", rating: 3 },
    { id: 3, name: "Hulk", rating: 5 }
  ],
  middle: [
    { id: 1, name: "Spider-man", rating: 4.5 },
    { id: 2, name: "Iron man", rating: 3.5 },
    { id: 3, name: "Hulk", rating: 1.5 }
  ],
  bottom: [
    { id: 1, name: "Spider-man", rating: 5 },
    { id: 2, name: "Iron man", rating: 2 },
    { id: 3, name: "Hulk", rating: 4 }
  ]
};

// Refactor code: using Dictionary along with `Logical nullish assignment (??=)` .
var result = Object.entries(obj).reduce((acc, [key, values]) => {
  values.forEach(v => {
    acc[v.id] ??= {...v, rating: {}};
    acc[v.id].rating[key] = v.rating;
  });
  
  return acc;
}, {});
console.log(Object.values(result));

/* Old versions: using Array approach

 var result = Object.entries(obj).reduce((acc, [key, values]) => {
  values.forEach(v => {
    var x = acc.find(r => r.id === v.id);
    if(x !== undefined){
      x.rating[key] = v.rating;
    }else{
        x = { id: v.id, name: v.name, rating: {[key]: v.rating} };
        acc.push(x);
      }
  });
  
  return acc;
}, []);
*/

More detailed explanation:

The logical nullish assignment (x ??= y) operator only assigns if x is nullish (null or undefined).

Upvotes: 1

hgb123
hgb123

Reputation: 14871

You could do it in a one-liner way

const obj = {
  top: [
    { id: 1, name: "Spider-man", rating: 1 },
    { id: 2, name: "Iron man", rating: 3 },
    { id: 3, name: "Hulk", rating: 5 },
  ],
  middle: [
    { id: 1, name: "Spider-man", rating: 4.5 },
    { id: 2, name: "Iron man", rating: 3.5 },
    { id: 3, name: "Hulk", rating: 1.5 },
  ],
  bottom: [
    { id: 1, name: "Spider-man", rating: 5 },
    { id: 2, name: "Iron man", rating: 2 },
    { id: 3, name: "Hulk", rating: 4 },
  ],
};

const res = Array.from(
  Object.entries(obj)
    .flatMap(([rater, ratee]) =>
      ratee.map(({ id, name, rating }) => ({
        id,
        name,
        [rater]: rating,
      }))
    )
    .reduce(
      (acc, { id, ...restInfo }) =>
        acc.set(id, { ...(acc.get(id) || {}), ...restInfo }),
      new Map()
    )
).map(([id, { name, ...rating }]) => ({ id, name, rating }));

console.log(res);

Upvotes: 1

CertainPerformance
CertainPerformance

Reputation: 370619

You need to map one of the subarrays to find each character's rating, so I think your current approach is pretty reasonable. You can make it a bit less repetitive by making an array of the properties (top, middle, bot) beforehand, then iterating over them instead of listing each different one:

const obj = {
  top: [
    { id: 1, name: "Spider-man", rating: 1 },
    { id: 2, name: "Iron man", rating: 3 },
    { id: 3, name: "Hulk", rating: 5 }
  ],
  middle: [
    { id: 1, name: "Spider-man", rating: 4.5 },
    { id: 2, name: "Iron man", rating: 3.5 },
    { id: 3, name: "Hulk", rating: 1.5 }
  ],
  bottom: [
    { id: 1, name: "Spider-man", rating: 5 },
    { id: 2, name: "Iron man", rating: 2 },
    { id: 3, name: "Hulk", rating: 4 }
  ]
};

const props = ['top', 'middle', 'bottom'];
var result = obj.top.map(x => ({
  id: x.id,
  name: x.name,
  rating: Object.fromEntries(
    props.map(prop =>
      [prop, obj[prop].find(t => t.id === x.id).rating]
    )
  )
}));      
console.log(result);

Another approach that's less computationally complex:

const obj = {
  top: [
    { id: 1, name: "Spider-man", rating: 1 },
    { id: 2, name: "Iron man", rating: 3 },
    { id: 3, name: "Hulk", rating: 5 }
  ],
  middle: [
    { id: 1, name: "Spider-man", rating: 4.5 },
    { id: 2, name: "Iron man", rating: 3.5 },
    { id: 3, name: "Hulk", rating: 1.5 }
  ],
  bottom: [
    { id: 1, name: "Spider-man", rating: 5 },
    { id: 2, name: "Iron man", rating: 2 },
    { id: 3, name: "Hulk", rating: 4 }
  ]
};
const byName = {};
for (const [prop, arr] of Object.entries(obj)) {
  for (const item of arr) {
    byName[item.name] ??= { ...item, rating: {} };
    byName[item.name].rating[prop] = item.rating;
  }
}
 
console.log(Object.values(byName));

Upvotes: 3

user12582716
user12582716

Reputation:

const obj = {
  top: [
    { id: 1, name: "Spider-man", rating: 1 },
    { id: 2, name: "Iron man", rating: 3 },
    { id: 3, name: "Hulk", rating: 5 }
  ],
  middle: [
    { id: 1, name: "Spider-man", rating: 4.5 },
    { id: 2, name: "Iron man", rating: 3.5 },
    { id: 3, name: "Hulk", rating: 1.5 }
  ],
  bottom: [
    { id: 1, name: "Spider-man", rating: 5 },
    { id: 2, name: "Iron man", rating: 2 },
    { id: 3, name: "Hulk", rating: 4 }
  ]
};

var result = [];
for(let [key, values] of Object.entries(obj))
  for(let item of values){
    let x = result.find(r => r.id === item.id);
    if(x !== undefined){
      x.rating[key] = item.rating;
    }else{
        x = { id: item.id, name: item.name, rating: {[key]: item.rating} };
        result.push(x);
      }
  }
console.log(result);

Upvotes: 0

user15026033
user15026033

Reputation:

const obj = {
  top: [
    { id: 1, name: "Spider-man", rating: 1 },
    { id: 2, name: "Iron man", rating: 3 },
    { id: 3, name: "Hulk", rating: 5 }
  ],
  middle: [
    { id: 1, name: "Spider-man", rating: 4.5 },
    { id: 2, name: "Iron man", rating: 3.5 },
    { id: 3, name: "Hulk", rating: 1.5 }
  ],
  bottom: [
    { id: 1, name: "Spider-man", rating: 5 },
    { id: 2, name: "Iron man", rating: 2 },
    { id: 3, name: "Hulk", rating: 4 }
  ]
};


const tempArr1 = Object.keys(obj).reduce((arr, key) => {
  obj[key].forEach((item) => {
    arr = [...arr, { ...item, rating: { [key]: item.rating } }];
  });
  return arr;
}, []);

const result = tempArr1.reduce((arr, el) => {
  let tempObj = { ...el };
  const index = arr.findIndex((tempItem) => tempItem.id === tempObj.id);
  if (~index) {
    arr[index] = {
      ...tempObj,
      rating: {
        ...arr[index].rating,
        ...tempObj.rating
      }
    };
  } else {
    arr = [...arr, tempObj];
  }
  return arr;
}, []);

console.log(result);

Upvotes: 0

Related Questions