hi I am trying to create a object map from array of objects using reduce method but did n't find a way to add 2 properties as key . Let say I have array of objects like -
const students = [
{
name: "sam",
age: 26,
},
{
name: 'john",
age: 30,
}
]
i am trying to create a map like
{
sam_26:{
name: "sam",
age: 26,
}
}
my code for reduce function :
students.reduce((obj, student) => {
`${obj[student.name]}_${obj[student.age]}` = student;
return obj;
}, {});
this didn't work . any pointers will be helpful ..thanks!
Create the key with the values taken from the student object. Assign the current student to the obj (the accumulator) using the key:
const students = [{
name: "sam",
age: 26,
},
{
name: "john",
age: 30,
}
];
const result = students.reduce((obj, student) => {
const key = `${student.name}_${student.age}`;
obj[key] = student;
return obj;
}, {});
console.log(result);
A generic approach that uses a callback to create the key:
const keyBy = (arr, cb) =>
arr.reduce((r, o) => {
const key = cb(o);
r[key] = o;
return r;
}, {});
const students = [{"name":"sam","age":26},{"name":"john","age":30}];
const result = keyBy(students, (o) => `${o.name}_${o.age}`);
console.log(result);
You can't assign to the left side with a template literal like that. Try defining the property first, and then assigning it to the object:
const students = [ { name: "sam", age: 26, }, { name: 'john', age: 30, } ];
const finalObj = students.reduce((obj, student) => {
const prop = `${student.name}_${student.age}`;
obj[prop] = student;
return obj;
}, {});
console.log(finalObj);
Hopefully this snippet will be useful
const students = [{
name: "sam",
age: 26,
},
{
name: "john",
age: 30,
}
]
//Using reduce function to add value to the accumalator
var x = students.reduce(function(acc, curr, index) {
// Here acc is the object which is passed as argument,
//In this object checking if it has a key like sam_26 & so on
if (!acc.hasOwnProperty([curr['name'] + '_' + curr['age']])) {
//if not then add the key and add relevant vakues to it
acc[curr.name + '_' + curr.age] = {
name: curr.name,
age: curr.age
}
}
return acc;
}, {});
console.log(x)
I tried this script and it worked. Simply create variable name based on student name and age then assign back to the object
students.reduce((obj, student) => {
var name = student.name + '-' + student.age;
obj[name] = student;
return obj;
}, {});
Related
I have an object similar to this:
const obj = {
id: 1,
name: {
"english-us": "John",
"english-uk": "John",
"italian-eu": "Giovanni",
},
};
I want to transfrorm every property name that is a string into a non-string one, like this:
const obj = {
id: 1,
name: {
english_us: "John",
english_uk: "John",
italian_eu: "Giovanni",
},
};
I can't modify the original object. I get it from an axios request.
You could use regex with stringify
let output = JSON.parse(JSON.stringify(obj).replace(/"(.*?)":.*?,?/g,
key=>key.replace(/\-/g, `_`)));
Output
console.log(JSON.stringify(output, null, 4));
/*
{
"id": 1,
"name": {
"english_us": "John",
"english_uk": "John",
"italian_eu": "Giovanni"
}
}*/
If you can copy the object, you could check this solution for declaring the attributes:
link
There are a few ways of achieving this. This example has a function that converts the key on every iteration of the name entries. A new names object is updated with these properties, and is later folded into a new object along with the existing properties of the original object.
const obj = {
id: 1,
name: {
"english-us": "John",
"english-uk": "John",
"italian-eu": "Giovanni",
},
};
const convert = (key) => key.replace('-', '_');
const updatedName = {};
for (const [key, value] of Object.entries(obj.name)) {
updatedName[convert(key)] = value;
}
const newObj = { ...obj, name: updatedName };
console.log(newObj);
You can convert object to JSON and convert back.
const obj = {
id: 1,
name: {
"english-us": "John",
"english-uk": "John",
"italian-eu": "Giovanni",
},
};
console.log(JSON.parse(JSON.stringify(obj)))
Two ways to clone the object and rename all keys from its name property
const obj = {
id: 1,
name: {
"english-us": "John",
"english-uk": "John",
"italian-eu": "Giovanni",
},
};
// clone obj
const myObj = window.structuredClone ?
structuredClone(obj) : JSON.parse(JSON.stringify(obj));
// rename all keys in myObj.name
Object.keys(myObj.name).forEach(key => {
myObj.name[key.replace(/\-/g, `_`)] = myObj.name[key];
delete myObj.name[key];
});
console.log(myObj.name.english_us);
// obj is untouched
console.log(obj.name[`english-us`]);
// myObj.name[`english-us`] does not exist
console.log(myObj.name[`english-us`]);
// alternative: clone and rename in one go
const myObjClone = {
...obj,
name: Object.fromEntries(
Object.entries(obj.name)
.reduce( (acc, [k, v]) =>
[ ...acc, [ k.replace(/\-/g, `_`), v ] ] , [] ) )
};
console.log(myObjClone.name.italian_eu);
// obj is untouched
console.log(obj.name[`italian-eu`]);
// myObjClone.name[`italian-eu`] does not exist
console.log(myObjClone.name[`italian-eu`]);
I am trying to group the people by their age and as you can see, my code works with the reduce method. I managed to get this working but now I want to do the same with forEach method. Here's my code:
{name:'Kyle', age:42},
{name:'Suk', age:34},
{name:'Lol', age:35},
{name:'Pol', age:23},
{name:'Kol', age:23}
]
people.reduce((groupedPeople, person)=>{
const age = person.age
if(groupedPeople[age] == null) {groupedPeople[age]=[]
}
groupedPeople[age].push(person)
return groupedPeople
})
Reduce accepts initial value that you update each iteration and return. So if you wish to use forEach, just move initial value before the forEach:
const people = [{
name: 'Kyle',
age: 42
},
{
name: 'Suk',
age: 34
},
{
name: 'Lol',
age: 35
},
{
name: 'Pol',
age: 23
},
{
name: 'Kol',
age: 23
}
]
const groupedPeople = {}
people.forEach((person) => {
const age = person.age
if (groupedPeople[age] == null) {
groupedPeople[age] = []
}
groupedPeople[age].push(person)
})
console.log(groupedPeople)
However, I am not sure why you wish to do that. Code with reduce is much cleaner.
Why forEach is just reduce with an accumulative value. Might as well be "global".
var people = [
{name:'Kyle', age:42},
{name:'Suk', age:34},
{name:'Lol', age:35},
{name:'Pol', age:23},
{name:'Kol', age:23}
];
var result = people.reduce((groupedPeople, person) => {
const age = person.age
if (groupedPeople[age] == null) {
groupedPeople[age] = []
}
groupedPeople[age].push(person)
return groupedPeople
}, {}) // <---- you forgot this {}
console.log(result)
var result = {};
people.forEach(function(person) {
if (result[person.age] == null) {
result[person.age] = []
}
result[person.age].push(person)
})
console.log(result)
.as-console-wrapper {
max-height: 100%!important;
top: 0;
}
I'm given the following JavaScript object:
{
name: [
{
"firstName":"First ",
"lastName":"Last"
}
],
age: 21
}
The name property (or other similar complex props) are always provided as an object within an array, even though there can only ever be a single value.
I need to save the information as an object that looks like this (without the array around the value of the name property):
{
name: {
firstName: 'First ',
lastName: 'Last'
},
age: 21
}
I need a generic function that doesn't reference a particular property name because that changes depending on the query. Here's my solution:
const object = {
name: [{"firstName":"First ","lastName":"Last"}],
age: 21
}
const data = {}
for (const property in object) {
const value = object[property]
if (Array.isArray(value)) {
data[property] = value[0]
} else {
data[property] = value
}
}
Which returns the properly formatted object.
My question is whether this is the most performant and/or most obvious way to get the result I'm looking for?
If you want abstraction over the entire object you could do something like this:
const object1 = {
name: [{"firstName":"First ","lastName":"Last"}],
age: 21
}
const rebuildObject = (object) => Object.keys(object).reduce((result, key) => {
const value = object[key];
result[key] = Array.isArray(value) ? object[key][0] : value;
return result;
}, {});
const newObject = rebuildObject(object1);
console.log(newObject);
If the name array is guaranteed to only ever have 1 object inside of it and is always an array, you can do:
const data = {
name: [
{
"firstName":"First ",
"lastName":"Last"
}
],
age: 21
};
if(data.name.length === 0) {
const newObj = {
name: data.name[0],
age: data.age
};
};
console.log(newObj); // { firstName: 'First ', lastName: 'Last', age: 21 }
Edit
When name is actually any arbitray key name, you can do:
const data = {
name: [
{
"firstName":"First ",
"lastName":"Last"
}
],
age: 21
};
const objKeys = Object.keys(data);
console.log(objKeys) // > Array ["name", "age"]
let arbKey = objKeys.filter(objKey => objKey !== "age")[0];
console.log(arbKey); // > "name"
const newObj = {
arbKey: data[arbKey][0],
age: data.age
};
console.log(newObj); // > Object { arbKey: Object { firstName: "First ", lastName: "Last" }, age: 21 };
Note: This only works based on the object schema you have provided. If your actual code is different, you will need to tweak it.
this is a generic function that can serve what you need, call the function with the object and property name you want to transform.
function arrayToObject(object, property) {
if(object[property] && Array.isArray(object[property])) {
object[property] = object[property][0];
}
return object;
}
// let data = {
// name: [
// {
// "firstName":"First ",
// "lastName":"Last"
// }
// ],
// age: 21
// }
// console.log(arrayToObject(data, 'name'));
// { name: { firstName: 'First ', lastName: 'Last' }, age: 21 }
update:
in case we don't know the property name,
we can use this version.
function arrayToObject(object) {
for(let key in object){
if(Array.isArray(object[key])) {
object[key] = object[key][0];
}
}
return object;
}
let a = {
name: [
{
"firstName":"First ",
"lastName":"Last"
}
],
age: 21
}
a.name = a.name[0];
This function takes an array of driver objects as the first argument and a JavaScript object that specifies an attribute and corresponding value.
For example, exactMatch(drivers, { revenue: 3000 }) will return all drivers whose revenue attribute equals 3000, and exactMatch(drivers, { name: 'Bob' }) will return all drivers whose name attribute equals Bob.
I have tried setting it up with map and filter with no success. Im trying to rewrite this code using ES6 format with map or filter, or otherwise just refactor for better code. Any help would be appreciated! I am very new to programming and JS in general.
function exactMatch(drivers, obj){
const driverMatch = [];
for (const driver of drivers){
for (const key in obj){
if (driver[key] === obj[key]){
driverMatch.push(driver);
}
}
}
return driverMatch;
}
Refactor for better code and to use JS ES6.
I would just generate a predicate from the second parameter. Since it contains a keys and a values, then Object.entries will de-compose it into those and you can generate a predicate from it that simply runs Array#every on each key-value pair to check if an object matches each of those:
const makePredicate = template => {
const keyValues = Object.entries(template);
return obj => keyValues.every(([key, value]) => obj[key] === value)
}
const predicate = makePredicate({name: "Bob"});
const a = {name: "Alice"};
const b = {name: "Bob"};
console.log(predicate(a));
console.log(predicate(b));
So you can use this predicate in Array#filter directly afterwards:
const makePredicate = template => {
const keyValues = Object.entries(template);
return obj => keyValues.every(([key, value]) => obj[key] === value)
}
const drivers = [{
name: "Alice",
revenue: 20000,
colour: "red"
},
{
name: "Bob",
revenue: 10000,
colour: "blue"
},
{
name: "Carol",
revenue: 10000,
colour: "red"
}
]
console.log(drivers.filter(makePredicate({
name: "Bob"
})));
console.log(drivers.filter(makePredicate({
revenue: 10000,
colour: "red"
})));
If you want to only match at least one of the key-values supplied, then you can swap and use Array#some:
const makePredicate = template => {
const keyValues = Object.entries(template);
return obj => keyValues.some(([key, value]) => obj[key] === value)
// ^^^^
}
const drivers = [{
name: "Alice",
revenue: 20000,
colour: "red"
},
{
name: "Bob",
revenue: 10000,
colour: "blue"
},
{
name: "Carol",
revenue: 10000,
colour: "red"
}
]
console.log(drivers.filter(makePredicate({
name: "Bob"
})));
console.log(drivers.filter(makePredicate({
revenue: 10000,
colour: "red"
})));
Short answer:
function exactMatch(drivers, obj) {
return drivers.filter(driver => {
const truths = Object.keys(obj).map(k => driver[k] === obj[k])
return truths.filter(Boolean).length === truths.length
})
}
There are several ways you could do this, but this was the first way that came to me. This is assuming you could pass an obj like { name: 'Bob', salary: 20000 } and require both of those conditions to be true.
You could get the entries of obj in advance and filter the array and check with Array#some, if one key/value pair match.
// one property match
function exactMatch(drivers, obj) {
var entries = Object.entries(obj);
return drivers.filter(driver => entries.some(([k, v]) => driver[k] === v));
}
For all properties who should match, you could take Array#every.
// all properties have to match
function exactMatch(drivers, obj) {
var entries = Object.entries(obj);
return drivers.filter(driver => entries.every(([k, v]) => driver[k] === v));
}
const exampleData = {
drivers: [
{
name: 'Bob',
revenue: 5000,
},
{
name: 'Alice',
revenue: 3000
},
{
name: 'Allen',
revenue: 4000
},
{
name: 'Monkey',
revenue: 5000
}
]
};
// Finds the first occurance
function exactMatch(exampleData, target) {
const targetKey = Object.keys(target)[0]
const targetValue = Object.values(target)[0]
return exampleData.drivers.find(driver => driver[targetKey] === targetValue)
}
// Finds all of the data that met the condition
function matchAll(exampleData, target) {
const targetKey = Object.keys(target)[0]
const targetValue = Object.values(target)[0]
return exampleData.drivers.filter(driver => driver[targetKey] === targetValue)
}
const resultA = exactMatch(exampleData, {revenue: 4000})
console.log(resultA)
const resultB = matchAll(exampleData, {revenue: 5000})
console.log(resultB)
I'm learning JS. Supposing I have the below array of objects:
var family = [
{
name: "Mike",
age: 10
},
{
name: "Matt"
age: 13
},
{
name: "Nancy",
age: 15
},
{
name: "Adam",
age: 22
},
{
name: "Jenny",
age: 85
},
{
name: "Nancy",
age: 2
},
{
name: "Carl",
age: 40
}
];
Notice that Nancy is showing up twice (changing only the age). Supposing I want to output only unique names. How do I output the above array of objects, without duplicates? ES6 answers more than welcome.
Related (couldn't find a good way for usage on objects):
Remove Duplicates from JavaScript Array
Easiest way to find duplicate values in a JavaScript array
EDIT Here's what I tried. It works well with strings but I can't figure how to make it work with objects:
family.reduce((a, b) => {
if (a.indexOf(b) < 0 ) {
a.push(b);
}
return a;
},[]);
You could use a Set in combination with Array#map and a spread operator ... in a single line.
Map returns an array with all names, which are going into the set initializer and then all values of the set are returned in an array.
var family = [{ name: "Mike", age: 10 }, { name: "Matt", age: 13 }, { name: "Nancy", age: 15 }, { name: "Adam", age: 22 }, { name: "Jenny", age: 85 }, { name: "Nancy", age: 2 }, { name: "Carl", age: 40 }],
unique = [...new Set(family.map(a => a.name))];
console.log(unique);
For filtering and return only unique names, you can use Array#filter with Set.
var family = [{ name: "Mike", age: 10 }, { name: "Matt", age: 13 }, { name: "Nancy", age: 15 }, { name: "Adam", age: 22 }, { name: "Jenny", age: 85 }, { name: "Nancy", age: 2 }, { name: "Carl", age: 40 }],
unique = family.filter((set => f => !set.has(f.name) && set.add(f.name))(new Set));
console.log(unique);
The Solution
Store occurrences of name external to the loop in an object, and filter if there's been a previous occurrence.
https://jsfiddle.net/nputptbb/2/
var occurrences = {}
var filteredFamily = family.filter(function(x) {
if (occurrences[x.name]) {
return false;
}
occurrences[x.name] = true;
return true;
})
you can also generalize this solution to a function
function filterByProperty(array, propertyName) {
var occurrences = {}
return array.filter(function(x) {
var property = x[propertyName]
if (occurrences[property]) {
return false;
}
occurrences[property]] = true;
return true;
})
}
and use it like
var filteredFamily = filterByProperty(family, 'name')
Explanation
Don't compare objects using indexOf, which only uses the === operator between objects. The reason why your current answer doesn't work is because === in JS does not compare the objects deeply, but instead compares the references. What I mean by that you can see in the following code:
var a = { x: 1 }
var b = { x: 1 }
console.log(a === b) // false
console.log(a === a) // true
Equality will tell you if you found the same exact object, but not if you found an object with the same contents.
In this case, you can compare your object on name since it should be a unique key. So obj.name === obj.name instead of obj === obj. Moreover another problem with your code that affects its runtime and not its function is that you use an indexOf inside of your reduce. indexOf is O(n), which makes the complexity of your algorithm O(n^2). Thus, it's better to use an object, which has O(1) lookup.
This will work fine.
const result = [1, 2, 2, 3, 3, 3, 3].reduce((x, y) => x.includes(y) ? x : [...x, y], []);
console.log(result);
With the code you mentioned, you can try:
family.filter((item, index, array) => {
return array.map((mapItem) => mapItem['name']).indexOf(item['name']) === index
})
Or you can have a generic function to make it work for other array of objects as well:
function printUniqueResults (arrayOfObj, key) {
return arrayOfObj.filter((item, index, array) => {
return array.map((mapItem) => mapItem[key]).indexOf(item[key]) === index
})
}
and then just use printUniqueResults(family, 'name')
(FIDDLE)
I just thought of 2 simple ways for Lodash users
Given this array:
let family = [
{
name: "Mike",
age: 10
},
{
name: "Matt",
age: 13
},
{
name: "Nancy",
age: 15
},
{
name: "Adam",
age: 22
},
{
name: "Jenny",
age: 85
},
{
name: "Nancy",
age: 2
},
{
name: "Carl",
age: 40
}
]
1. Find duplicates:
let duplicatesArr = _.difference(family, _.uniqBy(family, 'name'), 'name')
// duplicatesArr:
// [{
// name: "Nancy",
// age: 2
// }]
2 Find if there are duplicates, for validation purpose:
let uniqArr = _.uniqBy(family, 'name')
if (uniqArr.length === family.length) {
// No duplicates
}
if (uniqArr.length !== family.length) {
// Has duplicates
}
Since most of the answers won't have a good performance, i thought i share my take on this:
const arrayWithDuplicateData = [{ id: 5, name: 'Facebook'}, { id: 3, name: 'Twitter' }, { id: 5, name: 'Facebook' }];
const uniqueObj = {};
arrayWithDuplicateData.forEach(i => {
uniqueObj[i.id] = i;
});
const arrayWithoutDuplicates = Object.values(uniqueObj);
We're leveraging the fact that keys are unique within objects. That means the last duplication item inside the first array, will win over its predecessors. If we'd want to change that, we could flip the array before iterating over it.
Also we're not bound to use only one property of our object for identifying duplications.
const arrayWithDuplicateData = [{ id: 5, name: 'Facebook'}, { id: 3, name: 'Twitter' }, { id: 5, name: 'Facebook' }];
const uniqueObj = {};
arrayWithDuplicateData.forEach(item => {
uniqueObj[`${item.id}_${item.name}`] = item;
});
const arrayWithoutDuplicates = Object.values(uniqueObj);
Or we could simply add a check, if the uniqueObj already holds a key and if yes, not overwrite it.
Overall this way is not very costly in terms of performance and served me well so far.
I would probably set up some kind of object. Since you've said ECMAScript 6, you have access to Set, but since you want to compare values on your objects, it will take a little more work than that.
An example might look something like this (removed namespace pattern for clarity):
var setOfValues = new Set();
var items = [];
function add(item, valueGetter) {
var value = valueGetter(item);
if (setOfValues.has(value))
return;
setOfValues.add(value);
items.push(item);
}
function addMany(items, valueGetter) {
items.forEach(item => add(item, valueGetter));
}
Use it like this:
var family = [
...
];
addMany(family, item => item.name);
// items will now contain the unique items
Explanation: you need to pull a value from each object as it's added and decide if it has already been added yet, based on the value you get. It requires a value getter, which is a function that given an item, returns a value (item => item.name). Then, you only add items whose values haven't already been seen.
A class implementation:
// Prevents duplicate objects from being added
class ObjectSet {
constructor(key) {
this.key = key;
this.items = [];
this.set = new Set();
}
add(item) {
if (this.set.has(item[this.key])) return;
this.set.add(item[this.key]);
this.items.push(item);
}
addMany(items) {
items.forEach(item => this.add(item));
}
}
var mySet = new ObjectSet('name');
mySet.addMany(family);
console.log(mySet.items);