Reputation: 6928
I have an Javascript object that looks like this:
const obj = [
{
name: "Ali",
userID: 123,
type: "photo",
photo: {
"title": "Me",
"src": "/cool.jpg",
}
},
{
name: "Ali",
userID: 123,
type: "photo",
photo: {
"title": "Photo",
"src": "/photo.jpg"
}
},
{
name: "John",
userID: 1234,
type: "photo",
photo: {
"title": "Photo",
"src": "/photo.jpg"
}
},
{
name: "John",
userID: 1234,
type: "photo",
photo: {
"title": "Photo",
"src": "/photo.jpg"
}
}];
I need to loop over it and return an array that will look like:
const obj = [
{
name: "Ali",
userID: 123,
type: "photo",
photos: [
{
title: "Me",
src: "/cool.jpg"
},
{
title: "Photo",
src: "/photo.jpg"
}
]
},
{
name: "John",
userID: 1234,
type: "photo",
photos: [
{
"title": "Me",
"src": "photo.jpg"
},
{
title: "Photo",
src: "/photo.jpg"
}
]
}
]
Note that the first object can contain an object with the same name over 100 times, I need it only once and inside an array of all of his photos.. How would I do that? What type of loop should i use? Thanks...
Upvotes: 2
Views: 104
Reputation: 350841
Here is another ES6 script:
function merge(input) {
return [...input.reduce( (acc, {name, userID, type, photo} ) => {
let obj = acc.get(name) || {name, userID, type, photos: []};
obj.photos.push(photo);
return acc.set(name, obj);
}, new Map()).values()];
}
// Sample data
var input = [{
name: "Ali",
userID: 123,
type: "photo",
photo: {
"title": "Me",
"src": "/cool.jpg",
}
},
{
name: "Ali",
userID: 123,
type: "photo",
photo: {
"title": "Photo",
"src": "/photo.jpg"
}
},
{
name: "John",
userID: 1234,
type: "photo",
photo: {
"title": "Photo",
"src": "/photo.jpg"
}
},
{
name: "John",
userID: 1234,
type: "photo",
photo: {
"title": "Photo",
"src": "/photo.jpg"
}
}];
// convert
result = merge(input);
// output result
console.log(result);
input.reduce
iterates over the input, and accumulates a value that is initialised as a new Map (second argument to reduce
).
In each iteration the accumulated value is passed as first argument, and the second argument receives the input element. By a destructuring assignment, we get variables for each of the name
, userID
, type
and photo
properties.
There are three statements executed in each iteration:
obj
is defined either as the accumulated object we have for the name
value, or (||
) if we have none yet, as the given input element, but without the photo
property and with a new photos
array: {name, userID, type, photos: []}
.
The current photo is added to it.
The resulting object is assigned back to the accumulated Map object, which itself is returned to the reduce
internals. It will be passed as first argument for the next iteration, etc.
reduce
returns the final accumulated object, i.e. a Map. As this is not the desired output format, it is converted to an array, which is done by taking its values
(we don't need the keys any more), and converting that to an array with the spread operator ([... ]
).
Upvotes: 2
Reputation: 23492
Using ES6 and creating a new object with a copy of the data and therefore leaving the original data intact.
function merge(obj) {
const temp = new Map();
for (let r of obj) {
if (temp.has(r.userID)) {
temp.get(r.userID).photos.push(Object.assign({}, r.photo));
} else {
const entry = Object.assign({}, r);
entry.photos = [Object.assign({}, r.photo)];
delete entry.photo;
temp.set(r.userID, entry);
}
}
return Array.from(temp, x => x[1]);
}
const obj = [{
name: "Ali",
userID: 123,
type: "photo",
photo: {
"title": "Me",
"src": "/cool.jpg",
}
}, {
name: "Ali",
userID: 123,
type: "photo",
photo: {
"title": "Photo",
"src": "/photo.jpg"
}
}, {
name: "John",
userID: 1234,
type: "photo",
photo: {
"title": "Photo",
"src": "/photo.jpg"
}
}, {
name: "John",
userID: 1234,
type: "photo",
photo: {
"title": "Photo",
"src": "/photo.jpg"
}
}];
const merged = merge(obj);
document.getElementById('out').textContent = JSON.stringify(merged, null, 2);
console.log(merged);
<pre id="out"></pre>
Upvotes: 1
Reputation: 35477
A solution is to
In ES6
// Transform
let new_users = user.map(u => {
let o = Object.assign({}, u, {photos: [u.photo]}));
delete o.photo;
return o;
});
// Sort by user ID
new_users = new_users.sort((a,b) => a.userID - b.userID);
// Merge on userID
let prev = null;
new_user = new_user.filter(u => {
if (prev && prev.userID === u.userID) {
prev.photos.push(u.photos[0]);
return false;
}
prev = u;
return true;
});
Upvotes: 1
Reputation: 4041
This does most of what you want:
var sorted = {};
obj.forEach(function(element){
if(sorted[element.name]){
sorted[element.name].photos.push(element.photo)
}else{
sorted[element.name] = {
name: element.name,
userID: element.userID,
type: element.type,
photos:[element.photo]
}
}
});
This doesn't create an array it creates a object with the names as keys.
const obj = [
{
name: "Ali",
userID: 123,
type: "photo",
photo: {
"title": "Me",
"src": "/cool.jpg",
}
},
{
name: "Ali",
userID: 123,
type: "photo",
photo: {
"title": "Photo",
"src": "/photo.jpg"
}
},
{
name: "John",
userID: 1234,
type: "photo",
photo: {
"title": "Photo",
"src": "/photo.jpg"
}
},
{
name: "John",
userID: 1234,
type: "photo",
photo: {
"title": "Photo",
"src": "/photo.jpg"
}
}];
var sorted = {};
obj.forEach(function(element){
if(sorted[element.name]){
sorted[element.name].photos.push(element.photo)
}else{
sorted[element.name] = {
name: element.name,
userID: element.userID,
type: element.type,
photos:[element.photo]
}
}
console.log(sorted);
});
var sortedarray = sorted.map()
console.log(sortedarray);
You can add this bit of code to convert the object into an array:
var sortedarray = Object.keys(sorted).map(function(person){
return sorted[person]});
Upvotes: 1