Group an array by multiple fields - javascript

I have an api response that looks like this:
people = [
{
name: 'John',
surname: 'Doe',
pet: {
type: 'CAT',
name: 'whiskers',
age: 1
}
},
{
name: 'John',
surname: 'Doe',
pet: {
type: 'DOG',
name: 'Dexter',
age: 4
}
},
{
name: 'Jane',
surname: 'Doe',
pet: {
type: 'CAT',
name: 'Fluffy',
age: 10
}
},
{
name: 'Jane',
surname: 'Doe',
pet: {
type: 'CAT',
name: 'Dennis',
age: 3
}
}
]
I would like to translate it so that it looks like this (there are only ever two types of pet):
people = [
{
“name”: “John”,
“surname”: “Doe”,
“cats”: [
{
“name”: “whiskers”,
“age”: 1
}
],
“dogs”: [
{
“name”: “Dexter”,
“age”: 4
}
]
},
{
“name”: “Jane”,
“surname”: “Doe”,
“cats”: [
{
“name”: “Fluffy”,
“age”: 10
},
{
“name”: “Dennis”,
“age”: 3
}
]
}
]
I'm using angular 5. I need to be able to show a table similar to:
<table>
<thead>
<tr>
<th>Name</th>
<th>Surname</th>
<th>Cats</th>
<th>Dogs</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let person of people">
<td>{{person.name}}</td>
<td>{{person.surname}}</td>
<td>
<ul>
<li *ngFor="let cat of person.cats">name: {{cat.name}}, age: {{cat.age}} years</li>
</ul>
</td>
<td>
<ul>
<li *ngFor="let dog of person.dogs">name: {{dog.name}}, age: {{dog.age}} years</li>
</ul>
</td>
</tr>
</tbody>
</table>
I tried looping over building up a map as I went but I struggled to convert this back from a map to an array at the end. Also I was hoping there was a cleaner solution that I had missed:
const peopleMap = new Map;
this.people.forEach(person => {
const key = person.name + '_' + person.surname;
if (peopleMap[key]) {
if (person.pet.type === 'CAT') {
peopleMap[key].cats.push(new Pet(person.pet.name, person.pet.age));
} else {
peopleMap[key].dogs.push(new Pet(person.pet.name, person.pet.age));
}
} else {
let cats: [Pet];
let dogs: [Pet];
if (person.pet.type === 'CAT') {
cats.push(new Pet(person.pet.name, person.pet.age));
} else {
dogs.push(new Pet(person.pet.name, person.pet.age));
}
peopleMap[key] = new Person(person.name, person.surname, cats, dogs);
}
});
Ideally I will be allowed to change the api so that this logic sits server side. In the meantime I would love to know how to do this.

I would approach this by reducing the array ([see Array.prototype.reduce][1]
[1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce )
Basically, create an aggregate results array as you iterate through each (current) object in the "people" data. Check to see if an object with the person key exists. If they do, add the new pet to the appropriate pet array. Else, create a new person with the current pet.
Be sure to initialize the reduce function with an empty array (second argument when calling groupByPerson).
let people = [{name: 'John',surname: 'Doe',pet: {type: 'CAT',name: 'whiskers',age: 1}},{name: 'John',surname: 'Doe',pet: {type: 'DOG', name: 'Dexter',age: 4}},{name: 'Jane',surname: 'Doe',pet: {type: 'CAT',name: 'Fluffy',age: 10}},{name: 'Jane',surname: 'Doe',pet: {type: 'CAT',name: 'Dennis',age: 3}}];
function groupByPerson(data) {
return data.reduce( function (results, current) {
const key = current.name + "_" + current.surname;
let matched = results.filter(r => r.key === key)[0];
if (matched) {
let petArray = (current.pet.type === "CAT") ? matched.cats : matched.dogs;
petArray.push({name:current.pet.name, age:current.pet.age});
}
else {
let newPerson = {
key: key,
name: current.name,
surname: current.surname,
cats: [],
dogs: []
}
let petArray = (current.pet.type === "CAT") ? newPerson.cats : newPerson.dogs;
petArray.push({name:current.pet.name, age:current.pet.age});
results.push(newPerson);
}
return results;
}, []); // initialize with empty array
}
const results = groupByPerson(people);

You can use reduce to summarize your data to an object. Use Object.values to convert the object into an array
let people = [{name: 'John',surname: 'Doe',pet: {type: 'CAT',name: 'whiskers',age: 1}},{name: 'John',surname: 'Doe',pet: {type: 'DOG', name: 'Dexter',age: 4}},{name: 'Jane',surname: 'Doe',pet: {type: 'CAT',name: 'Fluffy',age: 10}},{name: 'Jane',surname: 'Doe',pet: {type: 'CAT',name: 'Dennis',age: 3}}];
let result = Object.values(people.reduce((c, {pet,...u}) => {
let k = u.name + u.surname; //Make a key using name and last name
let {type,...p} = pet; //Deconstrct pet object
type = type.toLowerCase(); //Make type lower case
c[k] = c[k] || u; //Initiate if name does not exist
c[k][type] = c[k][type] || []; //Initiate if pet does not exist
c[k][type].push(p); //Add the pet
return c;
}, {}));
console.log(result);

Fixing the answer to group:
First we create array with desired object structure
var newPeople = people.map(function(item){
var newPeople = {};
newPeople.name = item.name;
newPeople.surname = item.surname;
var type = item.pet.type;
newPeople[type] = [];
var petDetails = {
name: item.pet.name,
age: item.pet.age
}
newPeople[type].push(petDetails);
return newPeople;
});
/*Output: people = [{name: "John", surname: "Doe", CAT: Array(1)},
{name: "John", surname: "Doe", DOG: Array(1)},
{name: "Jane", surname: "Doe", CAT: Array(1)},
{name: "Jane", surname: "Doe", CAT: Array(1)}]*/
Now we will group them with below loop:
for (var i = 0; i < newPeople.length; i++) {
for(var j = i+1; j < newPeople.length;j++){
var item = newPeople[i];
var nextItem = newPeople[j];
if(item.name === nextItem.name && item.surname === nextItem.surname) {
var firstItemKeys = Object.keys(item);
var nextItemKeys = Object.keys(nextItem);
if(firstItemKeys[2] === nextItemKeys[2]) {
item[firstItemKeys[2]].push(nextItem[nextItemKeys[2]][0]);
} else {
if (Array.isArray(item[nextItemKeys[2]])) {
item[nextItemKeys[2]].push(nextItem[nextItemKeys[2]][0]);
} else {
item[nextItemKeys[2]] = [];
item[nextItemKeys[2]].push(nextItem[nextItemKeys[2]][0]);
}
}
newPeople.splice(j,1);
j--
}
}
}
The output is as expected:
newPeople = [{name: "John", surname: "Doe", CAT: Array(1), DOG: Array(1)},
{name: "Jane", surname: "Doe", CAT: Array(2)}]

Related

2 Array combine into one array javascript

Here is two array one is firstName another is lastName
{
firstName: ['name1', 'name2'],
lastName: ['last1', 'last2'],
}
but I want to formate the data into one array like this.
{
"name": [{
"firstName": "name1",
"lastName": "last1"
},
{
"firstName": "name2",
"lastName": "last2"
},
]
}
Using Array#map:
const getNameObj = (data = {}) => {
const { firstName = [], lastName = [] } = data;
if(firstName.length !== lastName.length) return;
const name = firstName.map((fn, i) => (
{ firstName: fn, lastName: lastName[i] }
));
return { name };
};
console.log(
getNameObj({ firstName: [ 'name1', 'name2' ], lastName: [ 'last1', 'last2' ] })
);
You could loop through it using for...in and push each element inside new array like this example
const data = {
firstName: [ 'name1', 'name2' ],
lastName: [ 'last1', 'last2' ],
}
let newData = [];
for (let el in data.firstName){
newData.push({
firstName: data.firstName[el],
lastName: data.lastName[el],
})
}
console.log(newData)
You can use Array.reduce to get the result.
const input = {
firstName: [ 'name1', 'name2' ],
lastName: [ 'last1', 'last2' ],
};
const result = input.firstName.reduce((acc, firstName, index) => {
acc.name.push({
firstName: firstName,
lastName: input.lastName[index]
})
return acc
}, {'name': []})
console.log(result);
You can build a new object {name: <value>}, and set your <value> to a mapped version of the firstName array, where each firstName from your array is mapped to an object of the form {firstName: ..., lastName: ...}. Here the firstName key has a value that is the current name, and the lastName key has a value from the lastName array at the corresponding index:
const obj = { firstName: ['name1', 'name2'], lastName: ['last1', 'last2'], };
const res = {
name: obj.firstName.map((firstName, i) => ({firstName, lastName: obj.lastName[i]}))
}
console.log(res);
here it is:
let obj = {firstName: ['name1', 'name2'],lastName: ['last1', 'last2']};
let finalObj = {};
finalObj.name = []
for (let i = 0; i < obj.firstName.length; i++)
finalObj.name.push(
{
firstName: obj.firstName[i],
lastName: obj.lastName[i]
}
)

How to nest element of an object inside the same object in javascript?

I created a form to get some info from User, and I want to move some of their info into a nested object. the reason why is to better organize my data later in front-end.
As a simple example, how to create the following "newInfo" out of "oldInfo" in JavaScript?
oldInfo = {
name: 'John',
Age: '32',
friend1: 'Michael',
friend2: 'Peter',
};
newInfo = {
name: 'John',
Age: '32',
friends: {
friend1: 'Michael',
friend2: 'peter',
},
};
I'm sure this must be a repeated and simple topic, but I couldn't find any as I didn't know what keyword to search for.
You could explicitly assign it
const oldInfo = {
name: "John",
Age: "32",
friend1: "Michael",
friend2: "Peter",
}
const newInfo = {
name: oldInfo.name,
Age: oldInfo.Age,
friends: {
friend1: oldInfo.friend1,
friend2: oldInfo.friend2,
},
}
console.log(newInfo)
You can do this easily with spread operator:
const { name, Age, ...friends } = oldInfo;
newInfo = { name, Age, friends };
It simply extracts all fields except name and age as friends.
Example:
const oldInfo = {
name: 'John',
Age: '32',
friend1: 'Michael',
friend2: 'Peter',
};
const { name, Age, ...friends } = oldInfo;
const newInfo = { name, Age, friends };
console.log(newInfo);
If you have a dynamic number of friend: name key-value pairs and other properties that shouldn't be nested into friends then you can use Object.entries and reduce:
const oldInfo = {
name: 'John',
Age: '32',
friend1: 'Michael',
friend2: 'Peter',
};
const newInfo = Object.entries(oldInfo).reduce((acc, [k, v]) => {
if(k.startsWith('friend')) {
acc.friends ? acc.friends[k] = v : acc.friends = {[k]: v};
} else {
acc[k] = v;
}
return acc;
},{});
console.log(newInfo);

How to find duplicates from list of array in angular 6 using some?

Hi I have list of values in array as bellow
var users = [{
name: 'John',
email: 'johnson#mail.com',
age: 25,
},
{
name: 'Tom',
email: 'tom#mail.com',
age: 35,
},
{
name: 'John',
email: 'johnson#mail.com',
age: 25,
}];
I should find duplicates row from the above array (need to compare all the fields name, email, and age)
I have used some function to find a duplicate value as below but need to pass multiple conditions in it. How to do that
const unique = new Set();
const showError = this.users.some(element => unique.size === unique.add(element.name).size);
As I have passed the name I need to verify email and age. How to do that
Maintain counter while checking equality, as every object will be equal with same object, hence check if counter is greater than 1 for getting status.
const status = users.some(user => {
let counter = 0;
for (const iterator of users) {
if (iterator.name === user.name && iterator.email === user.email && iterator.age === user.age) {
counter += 1;
}
}
return counter > 1;
});
var users = [
{
name: 'John',
email: 'johnson#mail.com',
age: 25,
},
{
name: 'Tom',
email: 'tom#mail.com',
age: 35,
},
{
name: 'John',
email: 'johnson#mail.com',
age: 25,
},
{
name: 'Tom',
email: 'tom#mail.com',
age: 35,
},,
{
name: 'Tom',
email: 'tom#mail.com',
age: 35,
},
{
name: 'Harry',
email: 'harry#mail.com',
age: 23,
},
{
name: 'Kane',
email: 'kane#mail.com',
age: 65,
},
{
name: 'Ron',
email: 'ron#mail.com',
age: 65,
},
{
name: 'Ron',
email: 'ron#mail.com',
age: 65,
}
];
// complexity of this function is n where n is the no of users
var data = uniqueData(users, 'email');
console.log(data)
function uniqueData(array, key) {
// create new objects for use
var uniqueArray = [];
var map = new Map();
// loop throught array
array.forEach((user,index) => {
// first item is always unique add to unique whithout check
if(index == 0) {
// using map set first item in map key and value is dynamic which we can set
map.set(array[index].email, array[index].email);
uniqueArray.push(array[index]);
}
//check if the key already exists if exists do not push else push
if (!map.get(user[key])) {
map.set(user[key], user[key]);
uniqueArray.push(user);
}
});
return uniqueArray;
}
Use below code for remove duplicates
function removeDuplicates(array, key) {
let lookup = {};
return array.filter(obj => !lookup[obj[key]] && lookup[obj[key]] = true);
}
Try this:
const showError: boolean = Array.from(new Set(users.map(user => JSON.stringify(user)))).length != users.length;
var frequency = users.reduce(function(seen, currentItem) {
if (currentItem in seen) {
seen[currentItem] = seen[currentItem] + 1;
} else {
seen[currentItem] = 1;
}
return seen;
}, {});
for (var key in frequency) {
if (frequency[key] > 1) {
result.push(key.split(",").map(function(currentItem) {
return parseInt(currentItem);
}));
}
}
console.log(result);
hope this will help you

How to distinct an object based off multiple attributes

I am looking for a way to distinct an array of objects, the method needs to distinct by two attributes for instance,
let arr = [
{
name: "George",
surname: "Hendricks"
},
{
name: "George",
surname: "Marques"
},
{
name: "George",
surname: "Hendricks"
}
]
Once filtered should only return an array of 2 objects, George Hendricks and George Marques As they are unique. Currently I can only filter with ES6 Set like so
let uniArr = [...(new Set(arr))]
How can I accomplish my task as fast as possible (working with big data)
If the property values are really strings, you can combine them to make a unique key, and then build a new array using an object (or Set) to track the ones you've already seen. The advantage to using an object or Set is that you don't have to re-scan the array every time to find out if an entry is unique. Lookup time on them is typically much better (even dramatically better) than a linear search.
Here's an example with an object:
let arr = [
{
name: "George",
surname: "Hendricks"
},
{
name: "George",
surname: "Marques"
},
{
name: "George",
surname: "Hendricks"
},
];
let seen = Object.create(null);
let filtered = arr.filter(entry => {
const key = entry.name + "\u0000" + entry.surname;
// ^---- a string you know won't be in either name or surname
const keep = !seen[key];
if (keep) {
seen[key] = true;
}
return keep;
});
console.log(filtered);
Or with a Set:
let arr = [
{
name: "George",
surname: "Hendricks"
},
{
name: "George",
surname: "Marques"
},
{
name: "George",
surname: "Hendricks"
},
];
let seen = new Set();
let filtered = arr.filter(entry => {
const key = entry.name + "\u0000" + entry.surname;
// ^---- a string you know won't be in either name or surname
const keep = !seen.has(key);
if (keep) {
seen.add(key);
}
return keep;
});
console.log(filtered);
You can use Array.filter() method to filter the array, by searching over the couple name and surname.
This is how should be your code:
var filtered = arr.filter((person, index, selfArray) =>
index === selfArray.findIndex((p) => (
p.name === person.name && p.surname === person.surname
))
);
Demo:
let arr = [{
name: "George",
surname: "Hendricks"
},
{
name: "George",
surname: "Marques"
},
{
name: "George",
surname: "Hendricks"
},
];
var filtered = arr.filter((person, index, selfArray) =>
index === selfArray.findIndex((p) => (
p.name === person.name && p.surname === person.surname
))
);
console.log(filtered);

How would I retrieve a property's value by knowing another property's value in an object?

I would like to not use jQuery if possible. What I'm looking for:
var obj = [
{
name: "Sam",
Job: "Developer"
},
{
name: "Mike",
Job: "Brother"
}
];
var testing = obj[name == sam].job // testing equals "Developer"
You are looking for Array.filter:
var obj = [
{
name: "Sam",
Job: "Developer"
},
{
name: "Mike",
Job: "Brother"
}
]
var testing = obj
.filter(function(person) {
return person.name == 'Sam'
}).map(function(person) {
return person.Job
})
console.log(testing)
or if you know that you're interested in the first one:
var obj = [
{
name: "Sam",
Job: "Developer"
},
{
name: "Mike",
Job: "Brother"
}
]
var testing = obj
.filter(function(person) {
return person.name == 'Sam'
}) var obj = [
{
name: "Sam",
Job: "Developer"
},
{
name: "Mike",
Job: "Brother"
}
]
var testing = obj
.filter(function(person) {
return person.name == 'Sam'
})
console.log(testing[0] && testing[0].Job)
and last but not least: If you know you have exactly one match, you can just do:
var testing = obj.filter(function(p) { return p.name == 'Sam'; })[0].Job
So, you can't do it as simply as you have written, but you can filter and find what you're looking for. I've made it a generic function to make life easier for reusability:
function getArrObjPropertyValue(arr, searchProperty, searchCompare, returnProperty) {
return arr.filter(function(item) {
return item[searchProperty] === searchCompare;
})[0][returnProperty];
}
console.log(getArrObjPropertyValue(obj, 'name', 'Sam', 'Job')); // Developer
Make the comparison for the object's key separately from assigning the value like this:
var people = [
{
name: "Sam",
Job: "Developer"
},
{
name: "Mike",
Job: "Brother"
}
];
var testing = null;
for (var person in people) {
if (people[person].name === 'Sam') { testing = people[person].job; }
}

Categories

Resources