Merging Nested Objects with same key into single Object in JavaScript - javascript

This is my Array in JavaScript, I want to merge teacher object below into combine single object to perform operations further.
const data = [
{
name: "ab",
class: 1,
grade: "A",
teacher: {
teacherName: "tab",
age: 34,
school: "ab pblc scl"
}
},
{
name: "cd",
class: 2,
grade: "B",
teacher: {
teacherName: "efg",
age: 35,
school: "cd pblc scl"
}
}
];
This is my Expected output. Here teacher object is combine with other single objects. Any idea how can I do this ?
const data = [
{
name: "ab",
class: 3,
grade: "B",
teacherName: "kio",
age: 38,
school: "ab pblc scl"
},
{
name: "de",
class: 2,
grade: "B",
teacherName: "tde",
age: 36,
school: "de pblc scl"
}
}
];
Any help will be appreciated

You could destructure teacher and spread the rest of the array with teacher to a new object.
Methods:
destructuring assignment to teacher,
result = data.map(({ teacher, ...object }) => ({ ...object, ...teacher }));
^ ^^^^^^^ ^
rest in object destructuring for getting all other properties,
result = data.map(({ teacher, ...object }) => ({ ...object, ...teacher }));
^ ^^^^^^^^^ ^
spread syntax ... for getting copies of own enumerable properties of the object
result = data.map(({ teacher, ...object }) => ({ ...object, ...teacher }));
^ ^^^^^^^^^ ^^^^^^^^^^ ^
const
data = [{ name: "ab", class: 1, grade: "A", teacher: { teacherName: "tab", age: 34, school: "ab pblc scl" } }, { name: "cd", class: 2, grade: "B", teacher: { teacherName: "efg", age: 35, school: "cd pblc scl" } }],
result = data.map(({ teacher, ...object }) => ({ ...object, ...teacher }));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

You can use spread operator to do it.
const data = [
{
name: "ab",
class: 1,
grade: "A",
teacher: {
teacherName: "tab",
age: 34,
school: "ab pblc scl"
}
},
{
name: "cd",
class: 2,
grade: "B",
teacher: {
teacherName: "efg",
age: 35,
school: "cd pblc scl"
}
}
];
const newData = data.map(d => {
const dClone = Object.assign({},d);
delete dClone.teacher;
return {
...dClone,
...d.teacher
}
})
console.log(newData);

Related

How do I combine objects properties in an array which have a common property in javascript

I have the below user information.
[ { ID: '21',NAME: 'JACK',MARKS: 75,GRADE: 'A',RESULT: 'PASS ' },
{ ID: '21',NAME: 'JACK',MARKS: 32,GRADE: 'F',RESULT: 'FAIL' },
{ ID: '87',NAME: 'SAM',MARKS: 91,GRADE: 'A+',RESULT: 'PASS' },
{ID: '7',NAME: 'TOM',MARKS: 100,GRADE: 'A+',RESULT: 'PASS' },
{ ID: '21',NAME: 'JACK',MARKS: 61,GRADE: 'B',RESULT: 'PASS' },
{ ID: '87',NAME: 'SAM',MARKS: 72,GRADE: 'B',RESULT: 'PASS ' }
]
Goal is to generate a new object called INFO with fields MARKS,GRADE,RESULT. Also since there are repetitive id's this INFO propery needs to be grouped with its respective id and generate the below result
[{ID:'21',INFO:[{MARKS: 75,GRADE: 'A',RESULT: 'PASS ' },{MARKS: 32,GRADE: 'F',RESULT: 'FAIL'},{MARKS: 61,GRADE: 'B',RESULT: 'PASS']},
{ID:'87',INFO:[MARKS: 91,GRADE: 'A+',RESULT: 'PASS'],[MARKS: 72,GRADE: 'B',RESULT: 'PASS ']},
{ID:'7',INFO:{ MARKS: 100,GRADE: 'A+,RESULT: 'PASS'}
]
I am trying to use the below code but it doesn't generate the expected results
console.log(groupByField(data,'id'))
function groupByField(data, field) {
var outObject = data.reduce(function (a, e) {
let estKey = (e[field]);
(a[estKey] ? a[estKey] : (a[estKey] = null || [])).push(e);
return a;
}, {});
return outObject
}
can someone help me?
function transform(info) {
return info.reduce((pre, cur) => {
let ext = pre.find(d => d.ID === cur.ID);
let {ID, MARKS, GRADE, RESULT} = cur;
if (ext) {
ext.INFO = ext.INFO instanceof Array
? [...ext.INFO, {MARKS, GRADE, RESULT}]
: [ext.INFO, {MARKS, GRADE, RESULT}];
} else {
pre.push({ID, INFO:{MARKS, GRADE, RESULT}});
}
return pre;
}, []);
}
Happy coding!
You can achieve the desired result using Map and forEach
const arr = [
{ ID: "21", NAME: "JACK", MARKS: 75, GRADE: "A", RESULT: "PASS " },
{ ID: "21", NAME: "JACK", MARKS: 32, GRADE: "F", RESULT: "FAIL" },
{ ID: "87", NAME: "SAM", MARKS: 91, GRADE: "A+", RESULT: "PASS" },
{ ID: "7", NAME: "TOM", MARKS: 100, GRADE: "A+", RESULT: "PASS" },
{ ID: "21", NAME: "JACK", MARKS: 61, GRADE: "B", RESULT: "PASS" },
{ ID: "87", NAME: "SAM", MARKS: 72, GRADE: "B", RESULT: "PASS " },
];
const map = new Map();
arr.forEach((o) => {
const { ID, NAME, ...rest } = o;
if (map.has(o.ID)) map.get(o.ID).push(rest);
else map.set(o.ID, [rest]);
});
const result = [];
for (let [ID, INFO] of map) {
if (INFO.length === 1) result.push({ ID, INFO: INFO[0] });
else result.push({ ID, INFO });
}
console.log(result);
Here is refactoring of the flagged duplicate to fit your specific needs, specifically grouping inside a nested array for each object.
It is a fairly standard 'group-by' using Array.prototype.reduce() in combination with destructuring assignment to isolate only the properties we need.
const input = [ { ID: '21', NAME: 'JACK', MARKS: 75, GRADE: 'A', RESULT: 'PASS ' }, { ID: '21', NAME: 'JACK', MARKS: 32, GRADE: 'F', RESULT: 'FAIL' }, { ID: '87', NAME: 'SAM', MARKS: 91, GRADE: 'A+', RESULT: 'PASS' }, { ID: '7', NAME: 'TOM', MARKS: 100, GRADE: 'A+', RESULT: 'PASS' }, { ID: '21', NAME: 'JACK', MARKS: 61, GRADE: 'B', RESULT: 'PASS' }, { ID: '87', NAME: 'SAM', MARKS: 72, GRADE: 'B', RESULT: 'PASS ' }, ];
const result = Object.values(
input.reduce(function (r, { ID, NAME, ...rest }) {
(r[ID] ??= { ID, INFO: [] }).INFO.push(rest);
return r;
}, Object.create(null))
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
The above snippet makes use of logical nullish assignment (??=). For compatibility you can use an || short-circuit instead if need be.
(r[ID] || (r[ID] = { ID, INFO: [] })).INFO.push(rest);
Using lodash groupBy() : https://lodash.com/docs/4.17.15#groupBy
const data = [
{ ID: '21', NAME: 'JACK', MARKS: 75, GRADE: 'A', RESULT: 'PASS ' },
{ ID: '21', NAME: 'JACK', MARKS: 32, GRADE: 'F', RESULT: 'FAIL' },
{ ID: '87', NAME: 'SAM', MARKS: 91, GRADE: 'A+', RESULT: 'PASS' },
{ ID: '7', NAME: 'TOM', MARKS: 100, GRADE: 'A+', RESULT: 'PASS' },
{ ID: '21', NAME: 'JACK', MARKS: 61, GRADE: 'B', RESULT: 'PASS' },
{ ID: '87', NAME: 'SAM', MARKS: 72, GRADE: 'B', RESULT: 'PASS ' },
];
const grouped = _.groupBy(data, 'NAME');
Results :
grouped:, {
JACK: [
{ ID: '21', NAME: 'JACK', MARKS: 75, GRADE: 'A', RESULT: 'PASS ' },
{ ID: '21', NAME: 'JACK', MARKS: 32, GRADE: 'F', RESULT: 'FAIL' },
{ ID: '21', NAME: 'JACK', MARKS: 61, GRADE: 'B', RESULT: 'PASS' }
],
SAM: [
{ ID: '87', NAME: 'SAM', MARKS: 91, GRADE: 'A+', RESULT: 'PASS' },
{ ID: '87', NAME: 'SAM', MARKS: 72, GRADE: 'B', RESULT: 'PASS ' }
],
TOM: [ { ID: '7', NAME: 'TOM', MARKS: 100, GRADE: 'A+', RESULT: 'PASS' } ]
}
An approach which transforms, groups and collects items within a single reduce task and no additional iterations within each reduce step might look similar to the following one ...
function groupCollectAndTransformItemById(collector, { ID, NAME, ...rest }) {
const { index, list } = collector;
let infoGroup = index[ID]?.INFO
if (Array.isArray(infoGroup)) {
// push into an already existing grouped array.
infoGroup.push(rest);
} else if (infoGroup) {
// create an array for more than just one grouped item.
index[ID].INFO = [infoGroup, rest];
} else {
// create a new group-item with
// its initial ID and first INFO data ...
infoGroup = index[ID] = { ID, INFO: rest };
// ... and push a reference into the collector's/
// accumulator's `list` array which also holds
// the final result of the reduce process.
list.push(infoGroup);
}
return collector;
}
const sampleData = [
{ ID: '21',NAME: 'JACK', MARKS: 75, GRADE: 'A', RESULT: 'PASS' },
{ ID: '21',NAME: 'JACK', MARKS: 32, GRADE: 'F', RESULT: 'FAIL' },
{ ID: '87',NAME: 'SAM', MARKS: 91, GRADE: 'A+', RESULT: 'PASS' },
{ ID: '7', NAME: 'TOM', MARKS: 100, GRADE: 'A+', RESULT: 'PASS' },
{ ID: '21',NAME: 'JACK', MARKS: 61, GRADE: 'B', RESULT: 'PASS' },
{ ID: '87',NAME: 'SAM', MARKS: 72, GRADE: 'B', RESULT: 'PASS' },
];
console.log(
sampleData
.reduce(groupCollectAndTransformItemById, {
index: {},
list: [],
}).list
);
.as-console-wrapper { min-height: 100%!important; top: 0; }

Group array of objects by different keys

I have an array of objects structured as below:
const myArr = [
{ name: "John", surname: "Smith", age: 18},
{ name: "Steve", surname: "Jones", age: 23},
{ name: "Mark", surname: "Green", age: 45},
{ name: "Anne", surname: "Williams", age: 34}
]
And I would like to group it like so:
[
{name: ["John", "Steve", "Mark", "Anne"]},
{surname: ["Smith", "Jones", "Green", "Williams"]},
{age: [18, 23, 45, 34]}
]
What's the best way? Tried with reduce() but no luck.
Assuming you know all objects have exactly the same keys, you can use Object.keys() function on the first element of the array, then iterate over the keys to create your objects
Object.keys(myArr[0]).map(key => {
const obj = {}; // Probably a better way to do that
obj[key] = myArr.map(item => item[key]);
return obj;
});
Nastier but shorter.
Object.keys(myArr[0]).map(key => ({ [key]: myArr.map(item => item[key]) }));
You could iterate the array, get all entries from the object and push to the array with the wanted key.
const
array = [{ name: "John", surname: "Smith", age: 18 }, { name: "Steve", surname: "Jones", age: 23 }, { name: "Mark", surname: "Green", age: 45 }, { name: "Anne", surname: "Williams", age: 34 }],
result = array.reduce((r, o) => {
Object.entries(o).forEach(([k, v]) => (r[k] ??= []).push(v));
return r;
}, {});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Use forEach and build an object with keys and aggregate values in array.
Now, create the array from above object entries.
const process = (arr) => {
const res = {};
arr.forEach((item) =>
Object.entries(item).forEach(([key, value]) =>
(res[key] ??= []).push(value)
)
);
return Object.entries(res).map(([key, value]) => ({ [key]: value }));
};
const myArr = [
{ name: "John", surname: "Smith", age: 18 },
{ name: "Steve", surname: "Jones", age: 23 },
{ name: "Mark", surname: "Green", age: 45 },
{ name: "Anne", surname: "Williams", age: 34 },
];
console.log(process(myArr));

JavaScript array map while keeping original elements in the array

I have this array.
const data = [
{ name: "A", age: "12" },
{ name: "B", age: "5" },
{ name: "C", age: "6" }
];
I want to add additional property called key to this array like this.
const data = [
{ name: "A", age: "12", key: "A12" },
{ name: "B", age: "5", key: "B5" },
{ name: "C", age: "6", key: "C6" }
];
I tired this map function and output is not the one I expect. How do I achieve this using JS map function..
const data = [
{ name: "A", age: "12"},
{ name: "B", age: "5"},
{ name: "C", age: "6"}
];
console.log(
"MAP",
data.map(element => (element.key = element.name + element.age))
);
You need to return an object from the map callback:
const data = [
{ name: "A", age: "12"},
{ name: "B", age: "5"},
{ name: "C", age: "6"}
];
console.log(
data.map(({ name, age }) => ({ name, age, key: name + age }))
);
If you wish to mutate the existing objects, then use forEach instead of .map:
const data = [
{ name: "A", age: "12"},
{ name: "B", age: "5"},
{ name: "C", age: "6"}
];
data.forEach((obj) => {
obj.key = obj.name + obj.age;
});
console.log(data);
Return element as well using the comma operator:
const data = [
{ name: "A", age: "12"},
{ name: "B", age: "5"},
{ name: "C", age: "6"}
];
console.log(data.map(element => (element.key = element.name + element.age, element)));
.as-console-wrapper { max-height: 100% !important; top: auto; }
Or just change your map callback to make it simpler with destructuring:
const data = [
{ name: "A", age: "12"},
{ name: "B", age: "5"},
{ name: "C", age: "6"}
];
console.log(data.map(({ name, age }) => ({ name, age, key: name + age})));
.as-console-wrapper { max-height: 100% !important; top: auto; }
There are more than one way to do this and the Array.map is by far the most concise and clean way to do it as already provided. Here are few other methods:
const data = [ { name: "A", age: "12" }, { name: "B", age: "5" }, { name: "C", age: "6" } ];
let from = Array.from(data, ({name, age}) => ({name, age, key: name+age}))
let assign = data.map(({name, age}) => Object.assign({name, age}, {key: name+age}))
console.log('from: ', from)
console.log('assign: ', assign)
If you want to generate a key from all values (assuming all are primitives) you can also do this:
const data = [
{ name: "A", age: "12", city: 'Miami' }, // "key": "A12Miami"
{ name: "B", age: "2", gender: 'M'}, // "key": "B2M"
];
let all = data.map(obj =>
({...obj, ...Object.fromEntries([['key', Object.values(obj).join('')]])}))
console.log(all)
This way you do not have to specify or care about the pop names etc.

Using nested reduces on an object by properties

I have data like this:
const data = [
{ id: 1, cat: "human", age: "10" },
{ id: 2, cat: "human", age: "20" },
{ id: 3, cat: "human", age: "10" },
{ id: 4, cat: "animal", age: "10" },
{ id: 5, cat: "animal", age: "20" },
{ id: 6, cat: "animal", age: "10" },
{ id: 7, cat: "alien", age: "10" },
{ id: 8, cat: "alien", age: "20" },
{ id: 9, cat: "alien", age: "10" },
];
I want to group this data something like that:
const gr = {
human: {
all: [
{ id: 1, cat: "human", age: "10" },
{ id: 2, cat: "human", age: "20" },
{ id: 3, cat: "human", age: "10" },
],
ages: {
"10": [
{ id: 1, cat: "human", age: "10" },
{ id: 3, cat: "human", age: "10" },
],
"20": [
{ id: 2, cat: "human", age: "20" },
],
}
},
animal: {...},
alien: {...},
}
I do first reduce like that:
const gr = data.reduce((acc, el) => {
const { cat } = el;
acc[cat] = acc[cat] || { all: [] };
acc[cat].all.push(el);
return acc;
}, {});
But I can't make a nested reduce here. I can do it separately like that:
const grAge = gr.human.all.reduce((acc,el) => {
const {age} = el;
acc[age] = acc[age] || [];
acc[age].push(el);
return acc;
},{});
gr.human["ages"] = grAge;
But obviously, this is not so efficient and needs more work. Maybe like this:
Object.keys(gr).forEach(key => {
const grAge = gr[key].all.reduce((acc,el) => {
const {age} = el;
acc[age] = acc[age] || [];
acc[age].push(el);
return acc;
},{});
gr[key]["ages"] = grAge;
});
Can I join those reduces in a single step?
If there are any other good methods I can use them, I don't need to use the reduce method.
You could take a sinle loop approach and assign the wanted structure to either allor to a nested strcture.
If you like to get a more dynamic version, you need to simplify the result structure for every nesting level (this means, the age level would contain an all property).
const
data = [{ id: 1, cat: "human", age: "10" }, { id: 2, cat: "human", age: "20" }, { id: 3, cat: "human", age: "10" }, { id: 4, cat: "animal", age: "10" }, { id: 5, cat: "animal", age: "20" }, { id: 6, cat: "animal", age: "10" }, { id: 7, cat: "alien", age: "10" }, { id: 8, cat: "alien", age: "20" }, { id: 9, cat: "alien", age: "10" }],
result = data.reduce((r, o) => {
r[o.cat] = r[o.cat] || { all: [], ages: {} };
r[o.cat].all.push(o);
r[o.cat].ages[o.age] = r[o.cat].ages[o.age] || [];
r[o.cat].ages[o.age].push(o);
return r;
}, {});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Another way to do this would be to get every unique category and ages using Sets, and then reducing them into your final JSON :
EDIT : It seems like the Stack Overflow snippet doesn't like it, but executing it in your browser console will give out the correct result
const data = [
{ id: 1, cat: "human", age: "10" },
{ id: 2, cat: "human", age: "20" },
{ id: 3, cat: "human", age: "10" },
{ id: 4, cat: "animal", age: "10" },
{ id: 5, cat: "animal", age: "20" },
{ id: 6, cat: "animal", age: "10" },
{ id: 7, cat: "alien", age: "10" },
{ id: 8, cat: "alien", age: "20" },
{ id: 9, cat: "alien", age: "10" },
];
const output = [...new Set(data.map(thing => thing.cat))].reduce((acc, category) => {
const catData = data.filter(thing => thing.cat === category)
return {
[category]: {
all: catData,
ages : [...new Set(catData.map(catThing => catThing.age))].reduce((catAcc, age) => ({
[age]: [...catData.filter(catThing => catThing.age === age)],
...catAcc
}), {})
},
...acc
}
}, {})
console.log(output)
.as-console-wrapper { max-height: 100% !important; top: 0; }

Filter array and reset the index too

I have an array of objects like this:
const data = [
{
name: "Peter",
age: 20,
nationality: "American",
index: 0
},
{
name: "David",
age: 25,
nationality: "English",
index: 1
},
{
name: "Gabriel",
age: 23,
nationality: "Spanish",
index: 2
},
{
name: "Kate",
age: 22,
nationality: "English",
index: 3
},
];
If I want to return a new array with only the people with English nationality I'd use filter, like this:
let englishPerson = data.filter(el => el.nationality === 'English');
console.log(englishPerson);
And this will log the following:
> Array [Object { name: "David", age: 25, nationality: "English", index: 1 }, Object { name: "Kate", age: 22, nationality: "English", index: 3 }]
But I would like to reset the index after the data is filtered, so the first object in the new filtered array should have an index of 0, second an index of 1 and so on. In this case David has an index of 1, because it kept the same index from the original data.
You could filter followed by map, but it would be better to do it in one go with reduce - if the item passes the test, add it to the accumulator, with the index of the accumulator's current length:
const data=[{name:"Peter",age:20,nationality:"American",index:0},{name:"David",age:25,nationality:"English",index:1},{name:"Gabriel",age:23,nationality:"Spanish",index:2},{name:"Kate",age:22,nationality:"English",index:3},]
console.log(
data.reduce((a, item) => {
if (item.nationality === 'English') {
a.push({
...item,
index: a.length
});
}
return a;
}, [])
);
You can use Array.prototype.map() to modify the index property of the filtered result:
const data = [
{
name: "Peter",
age: 20,
nationality: "American",
index: 0
},
{
name: "David",
age: 25,
nationality: "English",
index: 1
},
{
name: "Gabriel",
age: 23,
nationality: "Spanish",
index: 2
},
{
name: "Kate",
age: 22,
nationality: "English",
index: 3
},
];
let i=0;
let englishPerson = data.filter(el => el.nationality === 'English').map(el => {
el.index = i; i++;
return el;
});
console.log(englishPerson);
Updated answer based on your comments:
const data = [
{
name: "Peter",
age: 20,
nationality: "American",
index: 0
},
{
name: "David",
age: 25,
nationality: "English",
index: 1
},
{
name: "Gabriel",
age: 23,
nationality: "Spanish",
index: 2
},
{
name: "Kate",
age: 22,
nationality: "English",
index: 3
},
];
let i=0;
let englishPerson = data.filter(el => el.nationality === 'English').map(el => {
if(el.hasOwnProperty('index')){
el.index = i; i++;
}
return el;
});
console.log(englishPerson);
If you want use your solution, with the slightest change, you can try this:
var c=0;
var englishPerson=data.filter(function(el){
return el.nationality=="English" && (el.index=c++)>-1;
});
console.log(englishPerson);
Do this:
const data = [
{
name: "Peter",
age: 20,
nationality: "American",
index: 0
},
{
name: "David",
age: 25,
nationality: "English",
index: 1
},
{
name: "Gabriel",
age: 23,
nationality: "Spanish",
index: 2
},
{
name: "Kate",
age: 22,
nationality: "English",
index: 3
},
];
data.filter(el => Object.values(el).includes('English')).forEach((obj, i) => {obj.index = i; console.log(obj)})
If you want to go with map, then just replace forEachwith map!
You can use from filters.tags.data.map(f => f.Key).toString() replace filter.
Where you use from the map, that index been reset.
For example:
$scope.ret = function(){
data: url,
method: post,
retuen filters.tags.data.map(f => f.Key).toString();
}

Categories