Converting object array to hierarchical data structure - javascript

I have an array of objects and I would like to convert it into a different array. Each object in the original array has a category key and I would like the final result to group objects by category. I am trying to use the reduce method to do this but can not make sense of the examples I have found.
original array:
[
{category: "film", title: "toy story"},
{category: "film", title:"harry potter"},
{category: "tv", title:"seinfeld"}
]
desired result:
[
{
category: "film",
children: [
{title: "toy story"},
{title: "harry potter"}
],
}
{
category: "tv",
children: [
{title: 'seinfeld' }
]
}
]
I am trying to use d3 to create some graphs and the data needs to be sorted in a hierarchical structure. More on that here, https://github.com/d3/d3-hierarchy/blob/v1.1.9/README.md#hierarchy

Use reduce function,
first try to look if the item already exists in the array if yes, use the found index to locate the element and append to its children, if not add the initial item to the children array.
const input = [{
category: "film",
title: "toy story"
},
{
category: "film",
title: "harry potter"
},
{
category: "tv",
title: "seinfeld"
}
]
const result = input.reduce((acc, x) => {
const index = acc.findIndex(y => y.category === x.category)
if (index >= 0) {
acc[index].children.push({
title: x.title
})
} else {
acc = [...acc, {
category: x.category,
children: [{
title: x.title
}]
}]
}
return acc;
}, []);
console.log(result)
For not having child duplicates:
const result = input.reduce((acc, x) => {
const index = acc.findIndex(y => y.category === x.category)
const indexTitle = acc[index] && acc[index].children.findIndex(y => y.title === x.title)
if (index >= 0) {
if (indexTitle !== 0) {
acc[index].children.push({
title: x.title
})
}
} else {
acc = [...acc, {
category: x.category,
children: [{
title: x.title
}]
}]
}
return acc;
}, []);

You can use a reduce function:
const arr = [
{category: "film", title: "toy story"},
{category: "film", title: "harry potter"},
{category: "tv", title: "seinfeld"}
]
const arrByCategory = arr.reduce((acc, i) => {
// Check if the category already exist in the new array
const elIdx = acc.findIndex(_ => _.category === i.category);
const elExist = elIdx > -1;
if(elExist) {
// If the category exist, we just add the title to the children list
return acc[elIdx].children.push({title: i.title})
} else {
// If the category does not exist we create it and add the initial title in the children list
return acc.concat({
category: i.category,
children: [{ title: i.title }]
})
}
},[])
To give you a better understanding of the reduce function, here is a simple example:
const array = [1, 3, 6, 2, 5]
const sum = array.reduce((acc, i) => {
return acc + i;
}, 0)
console.log(sum) // 17

Related

get array of object value and nested array of object value

I've this nested array of object array:
const items = [
{
id: 1,
name: 'banana',
selected: true,
},
{
id: 2,
subItems: [
{
id: '2a',
name: 'apple',
selected: true,
},
{
id: '2b',
name: 'orange',
selected: false,
},
],
},
{
id: 3,
name: 'watermalon',
selected: true,
},
{
id: 4,
name: 'pear',
selected: false,
},
]
How can I get the ids base on selected property?
I manage to get the first level, I've tried
const selectedItemId = items.map(item => item.selected && item.id).filter(Boolean)
but how can I select the ids which is in the subItems? I expect the result to be [1, '2a', 3]
Flatten the array first. Be careful of using && item.id inside the mapper because that'll mean that falsey IDs (like 0, which is a reasonable starting number in some schemes) will be excluded.
const items=[{id:1,name:"banana",selected:!0},{id:2,subItems:[{id:"2a",name:"apple",selected:!0},{id:"2b",name:"orange",selected:!1}]},{id:3,name:"watermalon",selected:!0},{id:4,name:"pear",selected:!1}];
const output = items
.flatMap(item => [item].concat(item.subItems ?? []))
.filter(item => item.selected)
.map(item => item.id);
console.log(output);
You can recursively traverse all the items and select the items that have selected set to true.
const items = [
{ id: 1, name: "banana", selected: true },
{
id: 2,
subItems: [
{ id: "2a", name: "apple", selected: true },
{ id: "2b", name: "orange", selected: false },
],
},
{ id: 3, name: "watermalon", selected: true },
{ id: 4, name: "pear", selected: false },
];
function getSelectedItems(items, selectedItems = []) {
for (let item of items) {
if (item.subItems) {
getSelectedItems(item.subItems, selectedItems);
} else if (item.selected) {
selectedItems.push(item.id);
}
}
return selectedItems;
}
console.log(getSelectedItems(items));
let newArray = [];
items.forEach(i=>{
if(i.selected){
newArray.push(i.id)
}
if(i.subItems){
i.subItems.forEach(j=>{
if(j.selected){
newArray.push(j.id)
}
})
}
});
so this is bit lengthy. with 2 map loops
You can do:
const items=[{id:1,name:"banana",selected:!0},{id:2,subItems:[{id:"2a",name:"apple",selected:!0},{id:"2b",name:"orange",selected:!1}]},{id:3,name:"watermalon",selected:!0},{id:4,name:"pear",selected:!1}]
const output = items
.reduce((a, c) => [...a, c, ...(c.subItems || [])], [])
.filter(o => o.selected)
.map(({ id }) => id)
console.log(output)
Checking if a subItems array exsist in the item and recusively calling a function to extract selected Items will solve the issue.
function extractSubItems (items){
var selectItemsId = [];
selectItemsId = selectItemsId + items.map(item => {
if (item.selected===true){
return item.id;
}
if (item.subItems){
return extractSubItems(item.subItems);
}
}).filter(Boolean);
return selectItemsId
}
You can use Array#reduce in a nested fashion as follows:
const items = [ { id: 1, name: 'banana', selected: true, }, { id: 2, subItems: [ { id: '2a', name: 'apple', selected: true, }, { id: '2b', name: 'orange', selected: false, }, ], }, { id: 3, name: 'watermalon', selected: true, }, { id: 4, name: 'pear', selected: false, }, ],
output = items
.reduce(
(prev, {id,selected,subItems}) =>
subItems ?
selected ?
[...prev,id,...subItems.reduce( (p, {id:ID,selected:SEL}) => SEL ? [...p,ID] : p, [] )] :
[...prev,...subItems.reduce( (p, {id:ID,selected:SEL}) => SEL ? [...p,ID] : p, [] )] :
selected ?
[...prev,id] :
prev, []
);
console.log( output )
1 - Loop through items array
2 - if there is no subItems array then find the id of the item using condition
3 - if there is a subItems array then loop through that and find the id using condition
const result = []
items.map(item=>{
item.subItems ?
item.subItems.map(sub=>{
sub.selected && result.push(sub.id)
})
: item.selected && result.push(item.id)
})
console.log(result) // [1, "2a", 3]
This also works:
var ids = [
... items.filter(
it => it.selected || (it.subItems && it.subItems.some( sub => sub.selected ))
)
.map( it =>
it.subItems
? it.subItems.filter( it_sub => it_sub.selected ).map( it_sub => it_sub.id )
: [it.id]
)
].flat()
With resursive of subItems :
const items=[
{id:1,name:"banana",selected:!0},
{id:2,subItems:
[
{id:"2a",name:"apple",selected:!0},
{id:"2b",name:"orange",selected:!1},
{id:"2c",subItems:
[
{id:"2c1",name:"apple1",selected:!0},
{id:"2c1",name:"orange1",selected:!1}
]
},
]
},
{id:3,name:"watermalon",selected:!0},
{id:4,name:"pear",selected:!1}
];
const getSubItem = (obj) => {
let result = !obj.hasOwnProperty('subItems') ? [obj] : obj.subItems.reduce((res, item) => {
return res.concat(getSubItem(item))
}, [])
return result.filter(item => item.selected)
}
const result = items.reduce((res, item) => {
let subItem = getSubItem(item)
return res.concat(getSubItem(item))
}, [])
console.log(result)

Count array inside array of object

Suppose I have an array of Object as:
Expected O/P : {alpha:4, beta:8}
For this I tried as:
const apple = [{
name: 'alpha',
details: [{
"attachment": [123, 456]
}, {
"attachment": [1454, 1992]
}]
},
{
name: 'beta',
details: [{
"attachment": ["12", 189]
}, {
"attachment": ["maggi", 1890, 2000]
}, {
"attachment": [1990, 2001, 2901]
}]
}
];
const appleDetails = [];
for (let i = 0; i < apple.length; i++) {
for (let j = 0; j < apple[i].details.length; j++) {
const {
name
} = apple[i];
const {
attachment
} = apple[i].details[j]
appleDetails.push({
name,
attachment
})
}
}
let appleData = [];
appleDetails.forEach(({
name,
attachment
}) => {
attachment.forEach((attatchId) => {
appleData.push({
name,
_id: attatchId
})
})
})
const eachAppleCount = appleData.reduce((acc, item) => {
const key = item.name
if (!acc.hasOwnProperty(key)) {
acc[key] = 0
}
acc[key] += 1
return acc
}, {})
console.log(eachAppleCount);
This gives the O/P as required i.e. : {alpha:4, beta:8}
But the process involved is too much, is there any more efficient way such that,by using:
const apple = [{
name: 'alpha',
details: [{
"attachment": [123, 456]
}, {
"attachment": [1454, 1992]
}]
}, {
name: 'beta',
details: [{
"attachment": ["12", 189]
}, {
"attachment": ["maggi", 1890, 2000]
}, {
"attachment": [1990, 2001, 2901]
}]
}];
We can count the value for each apple name. If anyone needs any further information please do let me know.
This will do the work
const apple = [{
name: 'alpha',
details: [{
"attachment": [123, 456]
}, {
"attachment": [1454, 1992]
}]
}, {
name: 'beta',
details: [{
"attachment": ["12", 189]
}, {
"attachment": ["maggi", 1890, 2000]
}, {
"attachment": [1990, 2001, 2901]
}]
}];
const result = apple.reduce((acc, curr) => {
if (acc[curr.name] === undefined) acc[curr.name] = 0;
curr.details.forEach((d) => (acc[curr.name] += d.attachment.length));
return acc;
}, {});
console.log(result);
Try like below.
Array.prototype.map() - The map() method creates a new array populated with the results of calling a provided function on every element in the calling array.
Create new object as {[a.name]: values} where we use [a.name] inside [] so value from a.name will be set as object key.
To get value we use Array.prototype.flatMap() - The flatMap() method returns a new array formed by applying a given callback function to each element of the array, and then flattening the result by one level.
Get all attachments with a.details.flatMap(d => d.attachment) and use .length to get total count as value.
map will return result in format of array of objects.
Use Array.prototype.reduce() with spread syntax (...) to club array of objects into single object.
const apple = [
{ name: 'alpha', details: [{"attachment": [ 123, 456 ]}, {"attachment": [1454, 1992 ]} ]},
{name: 'beta', details: [{"attachment": [ "12", 189 ]},{"attachment": ["maggi", 1890,2000 ]}, {"attachment": [1990, 2001,2901 ]} ]}
];
// map will return result in format of array of objects
let resultArr = apple.map(a => ({[a.name]: a.details.flatMap(d => d.attachment).length }));
// use reduce with spread syntax (...) to club array of objects into single object.
let result = resultArr.reduce((i, a) => ({...a, ...i}), {});
console.log(result);

Javascript - Remove object from nested array

I have array of objects, each object must have key and title, but children is optional, and it can be nested, i can have children inside of children many times. I want to remove some object by provided key value (for example key 677). I tried with filter but i only remove first level. Also have tried recursion, but not sure if i did it right.
const data = [{
key: '1',
title: 'title 1',
children: [{
key: '098',
title: 'hey',
children: [{
key: '677',
title: 'child'
}]
}]
},
{
key: '123',
title: 'tile 111'
},
{
key: '345',
title: 'something'
}
];
const rem = '677';
const del = (el) => {
if (!el.children) {
return el.key !== rem;
} else {
if (el.key !== rem) {
del(el.children);
return el;
}
}
};
const res = data.filter((el) => {
return del(el);
});
console.log(res);
I guess your existing solution is like
const data = [
{
key: '1',
title: 'title 1',
children: [{
key: '098',
title: 'hey',
children: [{ key: '677', title: 'child'}]
}]
},
{ key: '123', title: 'tile 111' },
{ key: '345', title: 'something' }
];
function removeByKey(arr, removingKey){
return arr.filter( a => a.key !== removingKey);
}
So it works on the first level but not deeply.
Just change it like that will do the jobs
function removeByKey(arr, removingKey){
return arr.filter( a => a.key !== removingKey).map( e => {
return { ...e, children: removeByKey(e.children || [], removingKey)}
});
}
Little warning, children property will not be set to [] for every item not having any children.
So how it works? Well instead of keeping acceptable items as they are, we make a copy using {...e} that's equivalent to {key:e.key, title:e.title, children:e.children} in this case.
We know force to override the property children with removeByKey(e.children || [], removingKey), so we call the method recursively. Not the function works deeeply.
I would use a recursion approach with findIndex and splice. Using some will allow the code to exit without running through the entire tree.
const data = [{
key: '1',
title: 'title 1',
children: [{
key: '098',
title: 'hey',
children: [{
key: '677',
title: 'child'
}]
}]
},
{
key: '123',
title: 'tile 111'
},
{
key: '345',
title: 'something'
}
];
const removeKey = (data, key) => {
// look to see if object exists
const index = data.findIndex(x => x.key === key);
if (index > -1) {
data.splice(index, 1); // remove the object
return true
} else {
// loop over the indexes of the array until we find one with the key
return data.some(x => {
if (x.children) {
return removeKey(x.children, key);
} else {
return false;
}
})
}
}
console.log(removeKey(data, '677'))
console.log(JSON.stringify(data));
You can use some simple recursion to do the trick:
const data = [
{
key: '1',
title: 'title 1',
children: [
{
key: '098',
title: 'hey',
children: [{ key: '677', title: 'child'}]
}
]
},
{ key: '123', title: 'tile 111' },
{ key: '345', title: 'something' }
];
function removeByKey(key, arr) {
// loop through all items of array
for(let i = 0; i < arr.length; i++) {
// if array item has said key, then remove it
if(arr[i].key === key) {
arr.splice(i, 1);
} else if(typeof(arr[i].children) !== "undefined") {
// if object doesn't have desired key but has children, call this function
// on the children array
removeByKey(key, arr[i].children);
}
}
}
removeByKey('098', data);
console.log(data);
This may be a little easier to understand than the other answer provided.

How to sort the array of objects in javascript?

Hi i want to sort an array of objects in javascript. Below is the example data.
const example = [
{
name: "c_name",
children: [{
name: "child",
email: "child1#dev.com",
children: [{
name: "nested_child",
email: "nestedchild1#dev.com",
}]
}]
},
{
name: "a_name",
children: [{
name: "some_name",
email: "some_name#dev.com",
children: []
}]
},
{
name: "name",
children: [{
name: "child_name",
email: "child_name#dev.com",
children: []
}]
}
];
Should sort this array based on property 'name' and the children object should be sorted again based on 'name' property.
So the expected output is like below, and would like to retain other properties as well like email property in children.
a_name
some_name
c_name
child
nested_child
name
child_name
What i have done...i have a sort function that sorts the array by name property. however dont know how to sort the children object with name property.
const sorted_example = example.sort(this.sort_by_name());
sort_by_name = () => {
return (a, b) => {
let result;
const a_value = a.name.toLowerCase();
const b_value = b.name.toLowerCase();
if (a_value > b_value) {
result = 1;
} else if (a_value < b_value) {
result = -1;
} else {
result = 0;
}
return result;
};
};
Could someone help me how to continue with this. thanks.
The previous answers got you there most of the way but you need to sort again if an item has children. In my example I don't mutate the original array (use .slice to make a shallow copy so .sort doesn't mutate).
const example = [{"name":"c_name","children":[{"name":"child","email":"child1#dev.com","children":[{"name":"nested_child","email":"nestedchild1#dev.com"}]},{"name":"b"},{"name":"a"}]},{"name":"a_name","children":[{"name":"some_name","email":"some_name#dev.com","children":[]}]},{"name":"name","children":[{"name":"child_name","email":"child_name#dev.com","children":[]}]}];
const sortRecursive = (data) => {
const recur = (arr) =>
arr
.slice()
.sort((a, b) => a.name.localeCompare(b.name))
//check each item to see if it has children that is an array
.map(
(item) =>
//if item has children that is an array then sort that
// and it's childrens childrens children
Array.isArray(item.children)
? {
...item,
children: recur(item.children),
}
: item, //no children, just return the item
);
return recur(data);
};
//note that sortRecursive does not change example but returns a new array
// that is sorted
console.log(sortRecursive(example));
assuming your children are arrays instead of objects as per your example:
const example = [
{
name: "c_name",
children: [{
name: "child",
email: "child1#dev.com",
children: [{
name: "nested_child",
email: "nestedchild1#dev.com",
}]
}]
},
{
name: "a_name",
children: [{
name: "some_name",
email: "some_name#dev.com",
children: []
}]
},
{
name: "name",
children: [{
name: "child_name",
email: "child_name#dev.com",
children: []
}]
}
];
a quick way would be:
example
.sort((a, b) => a.name.localeCompare(b.name))
.map(m => {
return {
name: m.name,
children: m.children.sort((a, b) => a.name.localeCompare(b.name))
};
});
You can simply use the sort() method
example.sort((el, q) => el.name.localeCompare(q.name))

Get list of duplicate objects in an array of objects

I am trying to get duplicate objects within an array of objects. Let's say the object is like below.
values = [
{ id: 10, name: 'someName1' },
{ id: 10, name: 'someName2' },
{ id: 11, name: 'someName3' },
{ id: 12, name: 'someName4' }
];
Duplicate objects should return like below:
duplicate = [
{ id: 10, name: 'someName1' },
{ id: 10, name: 'someName2' }
];
You can use Array#reduce to make a counter lookup table based on the id key, then use Array#filter to remove any items that appeared only once in the lookup table. Time complexity is O(n).
const values = [{id: 10, name: 'someName1'}, {id: 10, name: 'someName2'}, {id: 11, name:'someName3'}, {id: 12, name: 'someName4'}];
const lookup = values.reduce((a, e) => {
a[e.id] = ++a[e.id] || 0;
return a;
}, {});
console.log(values.filter(e => lookup[e.id]));
Let's say you have:
arr = [
{ id:10, name: 'someName1' },
{ id:10, name: 'someName2' },
{ id:11, name: 'someName3' },
{ id:12, name: 'someName4' }
]
So, to get unique items:
unique = arr
.map(e => e['id'])
.map((e, i, final) => final.indexOf(e) === i && i)
.filter(obj=> arr[obj])
.map(e => arr[e]);
Then, result will be
unique = [
{ id:10, name: 'someName1' },
{ id:11, name: 'someName3' },
{ id:12, name: 'someName4' }
]
And, to get duplicate ids:
duplicateIds = arr
.map(e => e['id'])
.map((e, i, final) => final.indexOf(e) !== i && i)
.filter(obj=> arr[obj])
.map(e => arr[e]["id"])
List of IDs will be
duplicateIds = [10]
Thus, to get duplicates objects:
duplicate = arr.filter(obj=> dublicateIds.includes(obj.id));
Now you have it:
duplicate = [
{ id:10, name: 'someName1' },
{ id:10, name: 'someName2' }
]
Thanks https://reactgo.com/removeduplicateobjects/
You haven't clarified whether two objects with different ids, but the same "name" count as a duplicate. I will assume those do not count as a duplicate; in other words, only objects with the same id will count as duplicate.
let ids = {};
let dups = [];
values.forEach((val)=> {
if (ids[val.id]) {
// we have already found this same id
dups.push(val)
} else {
ids[val.id] = true;
}
})
return dups;
With lodash you can solve this with filter and countBy for complexity of O(n):
const data = [{ id: 10,name: 'someName1' }, { id: 10,name: 'someName2' }, { id: 11,name: 'someName3' }, { id: 12,name: 'someName4' } ]
const counts = _.countBy(data, 'id')
console.log(_.filter(data, x => counts[x.id] > 1))
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
You could do the same with ES6 like so:
const data = [{ id: 10,name: 'someName1' }, { id: 10,name: 'someName2' }, { id: 11,name: 'someName3' }, { id: 12,name: 'someName4' } ]
const countBy = (d, id) => d.reduce((r,{id},i,a) => (r[id] = a.filter(x => x.id == id).length, r),{})
const counts = countBy(data, 'id')
console.log(data.filter(x => [x.id] > 1))
You can use an array to store unique elements and use filter on values to only return duplicates.
const unique = []
const duplicates = values.filter(o => {
if(unique.find(i => i.id === o.id && i.name === o.name)) {
return true
}
unique.push(o)
return false;
})
With lodash you can use _.groupBy() to group elements by their id. Than _.filter() out groups that have less than two members, and _.flatten() the results:
const values = [{id: 10, name: 'someName1'}, {id: 10, name: 'someName2'}, {id: 11, name:'someName3'}, {id: 12, name: 'someName4'}];
const result = _.flow([
arr => _.groupBy(arr, 'id'), // group elements by id
g => _.filter(g, o => o.length > 1), // remove groups that have less than two members
_.flatten // flatten the results to a single array
])(values);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
An alternative based in #ggorlen solution with new Map() as accumulator (for better performance) and without unary operator ++ (not advised by default in projects with ESLint).
const values = [{ id: 10, name: "someName1" }, { id: 10, name: "someName2" }, { id: 11, name: "someName3" }, { id: 12, name: "someName4" },];
const lookup = values.reduce((a, e) => {
a.set(e.id, (a.get(e.id) ?? 0) + 1);
return a;
}, new Map());
console.log(values.filter(e => lookup.get(e.id) > 1));
Try this
function checkDuplicateInObject(propertyName, inputArray) {
var seenDuplicate = false,
testObject = {};
inputArray.map(function(item) {
var itemPropertyName = item[propertyName];
if (itemPropertyName in testObject) {
testObject[itemPropertyName].duplicate = true;
item.duplicate = true;
seenDuplicate = true;
}
else {
testObject[itemPropertyName] = item;
delete item.duplicate;
}
});
return seenDuplicate;
}
referred from : http://www.competa.com/blog/lets-find-duplicate-property-values-in-an-array-of-objects-in-javascript/

Categories

Resources