Ilanus
Ilanus

Reputation: 6928

Loop over array of objects, and generate new array by certain conditions

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

Answers (4)

trincot
trincot

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);

Explanation:

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:

  1. 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: []}.

  2. The current photo is added to it.

  3. 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

Xotic750
Xotic750

Reputation: 23492

Using ES6 and creating a new object with a copy of the data and therefore leaving the original data intact.

  1. Create a temporary Map.
  2. Loop each entry in the object.
  3. If the entry's ID does not exist in the Map then create and set a copy of the entry in the correct format.
  4. If an entry already exists then just push a copy of the photos to the entry.
  5. Return an array from the Map.

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

Richard Schneider
Richard Schneider

Reputation: 35477

A solution is to

  • transform each user to the new user (with an array of photos)
  • sort by the user's ID
  • merge photos based on user's ID

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

Niles Tanner
Niles Tanner

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

Related Questions