kathy
kathy

Reputation: 171

JavaScript array re structure

I have an array with student and parent addresses.

For example,

  const users = [{
    id: 1,
    name: 'John',
    email: '[email protected]',
    age: 25,
    parent_address: 'USA',
    relationship:'mother'
  },
  {
    id: 1,
    name: 'John',
    email: '[email protected]',
    age: 25,
    parent_address: 'Spain',
    relationship:'father'
  },
  {
    id: 2,
    name: 'Mark',
    email: '[email protected]',
    age: 28,
    parent_address: 'France',
    relationship:'father'
  }
];

I'm trying to reformat this to the following result.

const list = [
{
    id: 1,
    name: 'John',
    email: '[email protected]',
    age: 25,
    parent: [
        {
            parent_address: 'USA',
            relationship:'mother'
        },{
            parent_address: 'Spain',
            relationship:'father'
        }
    ]
},
{
    id: 2,
    name: 'Mark',
    email: '[email protected]',
    age: 28,
    parent:[
        {
            parent_address: 'France',
            relationship:'father'
        }
    ]
}
];

So far I tried the following way. I'm not sure that is the right way or not.

const duplicateInfo = [];
for (var i = 0; i < user[0].length; i++) {
    var parent = [];
    if (duplicateInfo.indexOf(user[0][i].id) != -1) {
        // Do duplicate stuff
    } else {
        // Do other
    }
    duplicateInfo.push(user[0][i].id);
}

Upvotes: 16

Views: 507

Answers (7)

JGFMK
JGFMK

Reputation: 8904

 const users = [{
    id: 1,
    name: 'John',
    email: '[email protected]',
    age: 25,
    parent_address: 'USA',
    relationship:'mother'
  },
  {
    id: 1,
    name: 'John',
    email: '[email protected]',
    age: 25,
    parent_address: 'Spain',
    relationship:'father'
  },
  {
    id: 2,
    name: 'Mark',
    email: '[email protected]',
    age: 28,
    parent_address: 'France',
    relationship:'father'
  }
];
ids = new Map()
for (const user of users) {
  var newuser;
  if (ids.has(user.id)) {
    newuser = ids.get(user.id);
  } else {
    newuser = {};
    newuser.id = user.id;
    newuser.name = user.name;
    newuser.email = user.email;
    newuser.age = user.age;
    newuser.parent = [];
  }
  relationship = {};
  relationship.parent_address = user.parent_address;
  relationship.relationship = user.relationship;
  newuser.parent.push(relationship)
  ids.set(user.id, newuser);
}
list = [ ...ids.values() ];
list.forEach((u) => {
  console.log(JSON.stringify(u));
});

Upvotes: 0

Nick Parsons
Nick Parsons

Reputation: 50954

One approach would be to use .reduce() with an object as an accumulator. For each id, you can store an associated object with a parents array which you can append to in your .reduce() callback whenever you encounter a new object with the same id. Then to get an array of objects from your object, you can call Object.values() on it

See example below:

const users = [{ id: 1, name: 'John', email: '[email protected]', age: 25, parent_address: 'USA', relationship: 'mother' }, { id: 1, name: 'John', email: '[email protected]', age: 25, parent_address: 'Spain', relationship: 'father' }, { id: 2, name: 'Mark', email: '[email protected]', age: 28, parent_address: 'France', relationship: 'father' } ];
const res = Object.values(users.reduce((acc, {parent_address, relationship, ...r}) => { // use destructuring assignment to pull out necessary values
  acc[r.id] = acc[r.id] || {...r, parents: []}
  acc[r.id].parents.push({parent_address, relationship}); // short-hand property names allows us to use the variable names as keys
  return acc;
}, {}));

console.log(res);

Since you mentioned you're new to JS, it may be easier to understand in a more imperative way (see code comments for details):

const users = [{ id: 1, name: 'John', email: '[email protected]', age: 25, parent_address: 'USA', relationship: 'mother' }, { id: 1, name: 'John', email: '[email protected]', age: 25, parent_address: 'Spain', relationship: 'father' }, { id: 2, name: 'Mark', email: '[email protected]', age: 28, parent_address: 'France', relationship: 'father' } ];

const unique_map = {}; // create an object - store each id as a key, and an object with a parents array as its value
for(let i = 0; i < users.length; i++) { // loop your array object
  const user = users[i]; // get the current object
  const id = user.id; // get the current object/users's id
  
  if(!(id in unique_map)) // check if current user's id is in the the object
    unique_map[id] = { // add the id to the unique_map with an object as its associated value 
      id: id,
      name: user.name,
      email: user.email,
      age: user.age,
      parents: [] // add `parents` array to append to later
    }
    
  unique_map[id].parents.push({ // push the parent into the object's parents array
    parent_address: user.parent_address,
    relationship: user.relationship
  });
}

const result = Object.values(unique_map); // get all values in the unique_map
console.log(result);

Upvotes: 12

Jason Goemaat
Jason Goemaat

Reputation: 29234

Restructuring data like this is pretty common and Array.reduce() is designed for the task. It is a different way of viewing things and takes some getting used to, but after you write the code a few times it becomes second nature.

reduce() is called on an array and takes two parameters:

  1. a function that will be called for each element in the array
  2. the starting value

Your function then is called for each element with the the starting value for the first run or the return value from the previous function call for each subsequent run, along the array element, index into the original array, and the original array that reduce() was called on (the last two are usually ignored and rarely needed). It should return the object or whatever you are building up with the current element added, and that return value gets passed to the next call to your function.

For things like this I usually have an object to keep the unique keys (id for you), but I see you want an array returned. That's one line to map the object and keys to an array and it's more efficient to use the build-in object property mechanism instead of array.find() to see if you have added an id already.

const users = [{
    id: 1,
    name: 'John',
    email: '[email protected]',
    age: 25,
    parent_address: 'USA',
    relationship:'mother'
  },
  {
    id: 1,
    name: 'John',
    email: '[email protected]',
    age: 25,
    parent_address: 'Spain',
    relationship:'father'
  },
  {
    id: 2,
    name: 'Mark',
    email: '[email protected]',
    age: 28,
    parent_address: 'France',
    relationship:'father'
  }
];

let combined = users.reduce(
  // function called for each element in the array
  (previous, element) => {
    // previous starts out as the empty object we pass as the second argument
    // and will be the return value from this function for every other element
    
    // create an object for the id on our 'previous' object if it doesn't exist,
    // if it does exist we will trust the name, email, and age from the first
    // instance
    previous[element.id] = previous[element.id] || {
      id: element.id,
      name: element.name,
      age: element.age,
      parents: []
    };
    
    // now add parent
    previous[element.id].parents.push({
      parent_address: element.parent_address,
      relationship: element.relationship
    });
    
    // return our updated object, which will be passed to the next call
    // and eventually returned
    return previous;
  },
  {} // initial value is an empty object, no ids yet
);

// transform object into array with elements in order by key
let list = Object.keys(combined).sort().map(key => combined[key]);

console.dir(list);

Upvotes: 2

Nina Scholz
Nina Scholz

Reputation: 386786

You could reduce the array and search for a user with the same id and add the parent information to it.

If the user is not found, add a new user to the result set.

const
    users = [{ id: 1, name: 'John', email: '[email protected]', age: 25, parent_address: 'USA', relationship: 'mother' }, { id: 1, name: 'John', email: '[email protected]', age: 25, parent_address: 'Spain', relationship: 'father' }, { id: 2, name: 'Mark', email: '[email protected]', age: 28, parent_address: 'France', relationship: 'father' }],
    grouped = users.reduce((r, { parent_address, relationship, ...user }) => {
        var temp = r.find(q => q.id === user.id );
        if (!temp) r.push(temp = { ...user, parent: []});
        temp.parent.push({ parent_address, relationship });
        return r;
    }, []);

console.log(grouped);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Upvotes: 5

StepUp
StepUp

Reputation: 38199

You can use Map collection to store unique items and just populate it using filter:

const unique = new Map(users.map(u=> 
    [u.id, {...u, parent: [...users.filter(f => f.id == u.id)]}]));

console.log(Array.from(unique, ([k, v])=> v)
    .map(s => ( { id: s.id, name: s.name, email: s.email, age:s.age, parent:s.parent })));

const users = [
  {
    id: 1,
    name: 'John',
    email: '[email protected]',
    age: 25,
    parent_address: 'USA',
    relationship: 'mother'
  },
  {
    id: 1,
    name: 'John',
    email: '[email protected]',
    age: 25,
    parent_address: 'Spain',
    relationship: 'father'
  },
  {
    id: 2,
    name: 'Mark',
    email: '[email protected]',
    age: 28,
    parent_address: 'France',
    relationship: 'father'
  }
];

const unique = new Map(users.map(u=> 
    [u.id, {...u, parent: [...users.filter(f => f.id == u.id)]}]));

console.log(Array.from(unique, ([k, v])=> v).map(s => ( 
    { id: s.id, name: s.name, email: s.email, age:s.age, parent:s.parent })));

Upvotes: 1

Maharjun M
Maharjun M

Reputation: 923

const users = [{
    id: 1,
    name: 'John',
    email: '[email protected]',
    age: 25,
    parent_address: 'USA',
    relationship:'mother'
  },
  {
    id: 1,
    name: 'John',
    email: '[email protected]',
    age: 25,
    parent_address: 'Spain',
    relationship:'father'
  },
  {
    id: 2,
    name: 'Mark',
    email: '[email protected]',
    age: 28,
    parent_address: 'France',
    relationship:'father'
  }
];
const updatedUsers = users.map(user => {
    return {
    id: user.id,
    name: user.name,
    email: user.email,
    age: user.age,
    parent: [{
        relationship: user.relationship,
        parent_address: user.parent_address,
    }]
}
})

const list = updatedUsers.reduce((acc, user) => {
    const findIndex = acc.findIndex(eachUser => eachUser.id === user.id && eachUser.email === user.email);
    if (findIndex < 0) {
        acc.push(user);
        return acc;
    } else {
    acc[findIndex].parent.push(user.parent);
    return acc; 
    }
}, []);
console.log(list)

Upvotes: 1

Anthony Poon
Anthony Poon

Reputation: 887

You need to iterate twice using the current method. The complexity is O(n^2). (for Loop + indexOf)

A better way is index the array and use the array key for duplication detection and search.

For example:

const map = {};
users.forEach(user => {
    // Will return undefined if not exist
    let existing = map[user.id];
    if (!existing) {
        // If not exist, create new
        existing = {
            id: user.id,
            ...
            parents: [ {parent_address: user.parent_address, relationship: user.relationship ]
        }
    } else {
        // Otherwise, update only parents field
        // You can add other logic here, for example update fields if duplication is detected.
        existing.parents.push({parent_address: user.parent_address, relationship: user.relationship ]
        });
    }
    map[user.id] = existing;
})
// Convert the object to array
const list = map.values();

Upvotes: 1

Related Questions