2

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

5
  • @RokoC.Buljan the array i am trying to achieve is totally different from this Commented Aug 25, 2016 at 23:03
  • yah... misread. Not totally but close. You actually want to merge photo of a user into multiple photos (which can be excerpt from here). What have you tried? You question as it stands shows no effort from your side. Do you have any code you already tried with a specific issue? Or you're trying to find someone to code this for you? Commented Aug 25, 2016 at 23:05
  • I have some code, but it's not doing exactly what i want, i need to stack those photos by the user id, i will add me code Commented Aug 25, 2016 at 23:07
  • Are wanting a new array of the data, or modifying the original obj? Commented Aug 25, 2016 at 23:13
  • @Xotic750 both are good for me Commented Aug 25, 2016 at 23:28

4 Answers 4

2

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 ([... ]).

Sign up to request clarification or add additional context in comments.

Comments

1

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

Comments

1

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

Comments

1

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>

2 Comments

This actually works in the way I need it to work except, I don't really understand how it works.. mind explaining? Thanks!
I've added a textual description of the workings, does that help?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.