How to compare object values in an array of objects? - javascript

I am trying to compare an array of objects. Each object has the same keys, but varying values for each key. I would like to create a function that compares each object for similar key value pairs.
I only care about the quality and location keys for each object, and I want to compare all objects against these two keys.
For example, if the first and second object in the array of objects contains the same value for two keys, I would like to create an output array of objects that summarizes each grouping.
Explanation: Object one and two contain the same value for quality and location. Since the third object does not, a new object should be created that summarizes the information from the first and second objects. That new object should contains an array of all fruit names and and array of all tags. The output object is shown below.
// Input
data = [
{
id: '1',
fruit: 'granny smith',
quality: 'good',
location: 'chicago',
tags: ['green', 'sweet'],
},
{
id: '2',
fruit: 'Fuji',
quality: 'good',
location: 'chicago',
tags: ['red', 'tart'],
},
{
id: '3',
fruit: 'gala',
quality: 'bad',
location: 'san diego',
tags: ['tall', 'thin'],
},
];
// Function
function compareObjects(arr) {
const grouped = [];
// Loop over every fruit
const similarObjects = arr.filter((obj, id) => {
// create structure for each common object
let shape = {
id,
fruits: [],
quality: '',
tags: [],
};
arr.forEach((item) => {
// Return a new shape object that contains all fruit names, tags, and quality
if (item.quality == obj.quality && item.location == obj.location) {
shape.id = id;
shape.fruits.push(item.fruit);
shape.fruits.push(obj.fruit);
shape.quality = item.quality;
shape.tags.push(item.tag);
shape.tags.push(obj.tag);
return shape;
}
});
return obj;
});
return similarObjects;
}
console.log(compareObjects(data));
// Output
const output = [
{
id: 'a',
fruits: ['grann smith', 'fuji'],
quality: 'good',
tags: ['green', 'sweet', 'red', 'tart'],
},
...
];

You can group the data by their quality and location using Array.prototype.reduce and filter the groups where the length is greater than one.
const
data = [
{ id: "1", fruit: "granny smith", quality: "good", location: "chicago", tags: ["green", "sweet"] },
{ id: "2", fruit: "Fuji", quality: "good", location: "chicago", tags: ["red", "tart"] },
{ id: "3", fruit: "gala", quality: "bad", location: "san diego", tags: ["tall", "thin"] },
],
output = Object.values(
data.reduce((r, d) => {
const key = `${d.quality}+${d.location}`;
if (!r[key]) {
r[key] = { id: d.id, fruits: [], quality: d.quality, location: d.location, tags: [] };
}
r[key].fruits.push(d.fruit);
r[key].tags.push(...d.tags);
return r;
}, {})
).filter((d) => d.fruits.length > 1);
console.log(output);
If you also wish to only keep unique fruits then you can map over the resultant array and remove the duplicates using a Set.
const
data = [
{ id: "1", fruit: "granny smith", quality: "good", location: "chicago", tags: ["green", "sweet"] },
{ id: "2", fruit: "Fuji", quality: "good", location: "chicago", tags: ["red", "tart"] },
{ id: "3", fruit: "gala", quality: "bad", location: "san diego", tags: ["tall", "thin"] },
],
output = Object.values(
data.reduce((r, d) => {
const key = `${d.quality}+${d.location}`;
if (!r[key]) {
r[key] = { id: d.id, fruits: [], quality: d.quality, location: d.location, tags: [] };
}
r[key].fruits.push(d.fruit);
r[key].tags.push(...d.tags);
return r;
}, {})
)
.filter((d) => d.fruits.length > 1)
.map((d) => ({ ...d, fruits: [...new Set(d.fruits)] }));
console.log(output);
Other relevant documentations:
Spread syntax (...)
Array.prototype.push
Array.prototype.filter
Object.values

Related

How to return a new array of objects that contains merged data from source and target?

I want to compare an oldItems array of objects and if id matches the id in my updatedItems array of objects, I want to update the object, copying over the property from oldItems if there is no value in updatedItems for that key or if that property is not defined, and replacing the oldItems object property with the updatedItems object property if there IS a value. I want to store all the changes in a result variable and log result to the console.
The result variable should contain exactly an object with id: 1, the new sandwich name, and the old price, as well as id: 2 with the new price and the new name.
const oldItems = [
{
id: 0,
name: "peanut butter sandwich",
price: 3
},
{
id: 1,
name: "cheese sandwich",
price: 4
},
{
id: 2,
name: "chicken sandwich",
price: 6
}
]
const updatedItems =
[{
id: 1,
name: "grilled cheese sandwich"
}, {
id: 2,
price: 5,
name: "chicken and waffles sandwich"
}]
I tried:
let result = oldItems.map((item, i) => Object.assign({}, item, updatedItems[i]));
console.log(result);
I'm not completely certain of your use-case, but this will produce the result you describe.
oldItems.map(o => {
const update = updatedItems.find(u => u.id === o.id);
if (update) return {
...o,
...update
}
}).filter(x => x)
result :
[
{ id: 1, name: 'grilled cheese sandwich', price: 4 },
{ id: 2, name: 'chicken and waffles sandwich', price: 5 }
]
First, your code maps over the wrong array, you should be mapping updatedItems.
Second, you can't use the same i for both arrays, since the items with the same id are not at the same indexes. Use find() to search the array for the id.
In ES6 you can use ... to merge objects instead of Object.assign().
const oldItems = [{
id: 0,
name: "peanut butter sandwich",
price: 3
},
{
id: 1,
name: "cheese sandwich",
price: 4
},
{
id: 2,
name: "chicken sandwich",
price: 6
}
];
const updatedItems = [{
id: 1,
name: "grilled cheese sandwich"
}, {
id: 2,
price: 5,
name: "chicken and waffles sandwich"
}];
let result = updatedItems.map((item) => ({
...oldItems.find(el => el.id == item.id),
...item
}));
console.log(result);

How to add property value from one array of objects into another (for the matching objects) in JS

I am trying to filter array of objects based on another array of objects and after finding all matching items I want to set property and value from the first array object into corresponding object in the second array:
const searchedProducts = products.filter(product =>
uniqueProducts.some(
uniqueProduct =>
product.productId === uniqueProduct.productId,
),
)
After here I need to set product.productName for each unique product object under productName property.
Ho such a thing can be achieved in a better way?
This is probably most straightforward using reduce() combined with find() to both retrieve and verify that the second array contains each object.
const uniqueProducts = [
{id: 3, from: 'uniqueProducts', name: 'Unique 1'},
{id: 12, from: 'uniqueProducts', name: 'Unique 12'}
];
const products = [
{id: 1, from: 'products', name: 'Product 1'},
{id: 2, from: 'products', name: 'Product 2'},
{id: 9, from: 'products', name: 'Product 9'},
{id: 12, from: 'products', name: 'Product 12'},
];
const output = products.reduce((a, p) => {
const uP = uniqueProducts.find(u => u.id === p.id);
if (uP) a.push({...p, name: uP.name});
return a;
}, []);
console.log(output);
I have inventory where I have to add a price property and take its value from products array so I did this,
inventory = [
{
"productId": 1,
"quantity": 100,
"name": "2 Yolks Noodles",
"image": "twoeggnoodles.jpg",
}
]
Products = [
{
"id": 1,
"quantity": 100,
"name": "2 Yolks Noodles",
"image": "twoeggnoodles.jpg",
"price": 34.95
}
]
let product:any = [];
products.map((prod:any)=>{
const index:any = inventory.find((u:any) => u.productId === prod.id);
// console.log("item", index, '-', prod)
if(index){
product.push({...index, price: prod.price});
}
return prod
})
});

How to replace a key inside a nested object

I have this list
{
'0': { id: 'id3', name: 'Capitan America', job: 'superHero' },
'1': { id: 'id4', name: 'Spider-man', job: 'Pro footballer' }
}
And i want to change my keys to the id value like this:
{
id3 : { id: 'id3', name: 'Capitan America', job: 'superHero' },
id4 : { id: 'id4', name: 'Spider-man', job: 'Pro footballer' }
}
this is what i have tried in my code where in my fetch items i transform an array of objects into an object of objects and when i transform into one object of object my keys stayed like the index of the array:
fetchItems() {
const objectified = Object.assign({},this.list)
return objectified;
}
When you use Object.assign to assign an array to an object, indices of the array will become properties of the assigned object. The best way to construct your desired object is to build it manually.
Modify your fetchItems as below:
function fetchItems() {
const result = {};
for (const item of list)
result[item.id] = item;
return result;
}
Here is one way to do what you are asking by stepping over each property, building a new object.
var objA = {
'0': { id: 'id3', name: 'Capitan America', job: 'superHero' },
'1': { id: 'id4', name: 'Spider-man', job: 'Pro footballer' }
};
function transform(obj) {
var newObj = {};
for(p in obj) { newObj[obj[p].id] = obj[p]; }
return newObj;
}
var objB = transform(objA);
console.log(objB);
You could use Object.entries and iterate through the key-value pairs with .reduce
const data = {
0: { id: "id3", name: "Capitan America", job: "superHero" },
1: { id: "id4", name: "Spider-man", job: "Pro footballer" },
}
const res = Object.entries(data).reduce(
(acc, el) => ({ ...acc, [el[1].id]: el[1] }),
{}
)
console.log(res)
Map the array to pairs of [id, object], and then convert to an object using Object.fromEntries():
const arr = [
{ id: 'id3', name: 'Capitan America', job: 'superHero' },
{ id: 'id4', name: 'Spider-man', job: 'Pro footballer' }
]
const result = Object.fromEntries(arr.map(o => [o.id, o]))
console.log(result)

How to groupBy array of objects based on properties in vanilla javascript

How do you groupBy array of objects based on specific properties in vanilla javascript? For example this given:
const products = [
{
category: "Sporting Goods",
price: "$49.99",
stocked: true,
name: "Football"
},
{
category: "Sporting Goods",
price: "$9.99",
stocked: true,
name: "Baseball"
},
{
category: "Sporting Goods",
price: "$29.99",
stocked: false,
name: "Basketball"
},
{
category: "Electronics",
price: "$99.99",
stocked: true,
name: "iPod Touch"
},
{
category: "Electronics",
price: "$399.99",
stocked: false,
name: "iPhone 5"
},
{ category: "Electronics", price: "$199.99", stocked: true, name: "Nexus 7" }
];
i want to run a reduce function that would result to a new array of objects like this:
Intended Output:
const categorize = [
{
category:"Sporting Goods",
products: [
{
name:"Football",
price: "$49.99",
stocked: true
},
{
name:"Baseball",
price: "$9.99",
stocked: true
},
{
name:"Basketball",
price: "$29.99",
stocked: true
}
]
},
{
category: "Electronics",
products: [
{
name: "iPod Touch",
price: "$99.99",
stocked: true
},
{
name: "iPhone 5",
price: "$399.99",
stocked: false
},
{
name: "Nexus 7",
price: "$199.99",
stocked: true
}
]
}
]
i based my solution from the tutorial here: https://www.consolelog.io/group-by-in-javascript/ using the reduce function.
Here's my code:
const groupBy = (arr,prop)=>{
return arr.reduce((groups,item)=>{
let val = item[prop];
groups[val] = groups[val]||[];
groups[val].push(item);
return groups
},{});
}
const categorize = groupBy(products,'category');
console.log(categorize);
/* returns an Object like
Object {Sporting Goods: Array[3], Electronics: Array[3]}
however it's not the intended output.
*/
I tried to return Object.values(obj) or Object.entries(obj) inside the groupBy function but it just returns an array of 2 arrays like [Array[3],Array[3]] and if i set the initial value (2nd parameter of reduce) to empty [] instead of {}, the output is just an empty array. Need help, thanks!
Because you want an array containing objects (rather than just an array of plain values), create a { category, products: [] } object if it doesn't exist in the accumulator:
const products=[{category:"Sporting Goods",price:"$49.99",stocked:!0,name:"Football"},{category:"Sporting Goods",price:"$9.99",stocked:!0,name:"Baseball"},{category:"Sporting Goods",price:"$29.99",stocked:!1,name:"Basketball"},{category:"Electronics",price:"$99.99",stocked:!0,name:"iPod Touch"},{category:"Electronics",price:"$399.99",stocked:!1,name:"iPhone 5"},{category:"Electronics",price:"$199.99",stocked:!0,name:"Nexus 7"}];
const output = Object.values(
products.reduce((a, { category, ...item }) => {
if (!a[category]) {
a[category] = { category, products: [] };
}
a[category].products.push(item);
return a;
}, {})
);
console.log(output);
function (){
var map = {};
products.forEach((p) => {
if (map[p.category]) {
var c = p.category;
delete p.category;
map[c].push(p);
} else {
var c = p.category;
delete p.category;
map[c]=[c]
}
});
Object.keys(map).forEach((m) => {
ans.push({category:m, products: map[m]})
})
}
you can collect in one go products and map them.
Then make your resultinng array

Array.filter() to remove duplicate Ojects

I would like to fuse Array.filter() function to remove duplicate objects
I am able to achieve in the case of string or integer arrays. But I am not able to achieve the same with array of objects as in the second case of names
const names = ['John', 'Paul', 'George', 'Ringo', 'John'];
let x = names => names.filter((v, i, arr) => arr.indexOf(v) === i);
console.log(x(names)); //[ 'John', 'Paul', 'George', 'Ringo' ]
const names = [
{ name: "John" },
{ name: "Paul" },
{ name: "George" },
{ name: "Ringo" },
{ name: "John" } ];
// returns the same original array
Could you please help?
Using Array#reduce() and a Map accumulator then spread the values() of the Map into array
const names = [
{ name: "John" },
{ name: "Paul" },
{ name: "George" },
{ name: "Ringo" },
{ name: "John" } ];
const unique = [... names.reduce((a,c)=>(a.set(c.name,c)),new Map).values()]
console.log(unique)
Use Array.reduce and Object.values
Iterate over the array and create an object with key as name and value as object from array. In case of objects with same name, the value will be overwritten in resultant object. Finally use Object.values to collect all the unique objects.
const names = [{ name: "John" },{ name: "Paul" },{ name: "George" },{ name: "Ringo" },{ name: "John" } ];
let result = Object.values(names.reduce((a,c) => Object.assign(a, {[c.name]:c}),{}));
console.log(result);
For tweaking - Plunker
const names = [
{ name: "John" },
{ name: "Paul" },
{ name: "George" },
{ name: "Ringo" },
{ name: "John" }
];
/* unique => Filter: Remove all duplicate items from an array. Works with plain objects as well, since we stringify each array item.
* #type public Function
* #name unique
* #return Function( item )
* #notes
*/
const unique = () => {
const seen = {};
return item => {
const json = JSON.stringify( item );
return seen.hasOwnProperty( json )
? false
: ( seen[ json ] = true );
};
};
const result = names.filter( unique() );
console.log( result );
You could use lodash's _uniqBy for this:
const names = [
{ name: "John" },
{ name: "Paul" },
{ name: "George" },
{ name: "Ringo" },
{ name: "John" } ];
const result = _uniqBy(names, 'name');
This can be done with the help of Sets as well
var names = [{ name: "John" },{ name: "Paul" },{ name: "George" },{ name: "Ringo" },{ name: "John" } ];
var result = Array.from(
names.reduce((s, d) => s.add(d.name), new Set)
, d => ({ name: d })
)
console.log(result)
Keith had a great suggestion to use findIndex with filter instead of indexOf. Object literals are always unique references, so we cannot compare them. We can however compare the name keys between the objects. We can do this with the aforementioned functions.
const names = [
{ name: "John" },
{ name: "Paul" },
{ name: "George" },
{ name: "Ringo" },
{ name: "John" }
];
console.log(names.filter(({name1}, i, a) => {
return i == a.findIndex(({name2}) => {
return name1 == name2;
});
});
const names = ['John', 'Paul', 'George', 'Ringo', 'John'];
function removeDups(names) {
let unique = {};
names.forEach(function(i) {
if(!unique[i]) {
unique[i] = true;
}
});
return Object.keys(unique);
}
removeDups(names); //'John', 'Paul', 'George', 'Ringo'

Categories

Resources