I have a nested array of objects. I would like to get an object that only provides me the onClick property as the key and the value as null. I am able to navigate to the first level, but I am unable to navigate down further. How do I go forward doing it?
const headers = [{
id: 'title1',
title: 'Title 1',
children: [
{
title: 'Children 1',
child: [
{
title: 'Child 1',
id: 'child1Id',
onClick: 'child1Click',
url: '/child1'
},
{
title: 'Child 2',
id: 'child2Id',
onClick: 'child2Click'
}
]
},
{
title: 'Children 2',
child: [
{
title: 'Child 3',
id: 'child3Id',
onClick: 'child3Click',
},
{
title: 'Child 4',
id: 'child4Id',
onClick: 'child4Click'
}
]
}
]
},
{
id: 'title2',
title: 'Title 2',
privilege: '',
children: [{
title: 'Children 3',
privilege: '',
child: [{
title: 'Child 5',
id: 'child5Id',
onClick: 'child3Click',
url: '/child5',
privilege: ''
},
{
title: 'Child 6',
id: 'child6Id',
onClick: 'child6Click',
url: '/child6',
privilege: ''
}
]
},
{
title: 'Children 4',
privilege: '',
child: [{
title: 'Child 7',
id: 'child7Id',
onClick: 'child7Click',
url: '/child7',
privilege: ''
},
{
title: 'Child 8',
id: 'child8Id',
onClick: 'child8Click',
url: '/child8',
privilege: ''
}
]
}
]
}
];
const routesMap = ({ onClick, children }) => (onClick ? { [onClick]: null } : _.flatMap(children, routesMap));
const routeStates = _.assign({}, ..._.flatMap(headers, routesMap));
console.log(routeStates)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
Expected Output:
{
child1Click: null,
child2Click: null,
child3Click: null,
child4Click: null,
child5Click: null,
child6Click: null,
child7Click: null,
child8Click: null,
}
Please advice. Any help is highly appreciated.
Using vanilla JS you can use 3 nested map loops to get the onClick values, and return a an object with the value as property. Use Array.flat() to convert to a single array, and spread into Object.assign() to get an object.
const fn = headers => Object.assign({}, ...headers
.map(({ children }) =>
children.map(({ child }) => child.map(({ onClick }) => ({ [onClick]: null })))
) // extract onClick values
.flat(Infinity) // flatten to a single array
)
const headers = [{"id":"title1","title":"Title 1","children":[{"title":"Children 1","child":[{"title":"Child 1","id":"child1Id","onClick":"child1Click","url":"/child1"},{"title":"Child 2","id":"child2Id","onClick":"child2Click"}]},{"title":"Children 2","child":[{"title":"Child 3","id":"child3Id","onClick":"child3Click"},{"title":"Child 4","id":"child4Id","onClick":"child4Click"}]}]},{"id":"title2","title":"Title 2","privilege":"","children":[{"title":"Children 3","privilege":"","child":[{"title":"Child 5","id":"child5Id","onClick":"child3Click","url":"/child5","privilege":""},{"title":"Child 6","id":"child6Id","onClick":"child6Click","url":"/child6","privilege":""}]},{"title":"Children 4","privilege":"","child":[{"title":"Child 7","id":"child7Id","onClick":"child7Click","url":"/child7","privilege":""},{"title":"Child 8","id":"child8Id","onClick":"child8Click","url":"/child8","privilege":""}]}]}]
const routeStates = fn(headers)
console.log(routeStates)
With lodash you can use _.flatMapDeep() instead of Array.flat():
const fn = headers => Object.assign({}, ...
_.flatMapDeep(headers, ({ children }) =>
_.map(children, ({ child }) => _.map(child, ({ onClick }) => ({ [onClick]: null })))
) // extract onClick values and flatte
)
const headers = [{"id":"title1","title":"Title 1","children":[{"title":"Children 1","child":[{"title":"Child 1","id":"child1Id","onClick":"child1Click","url":"/child1"},{"title":"Child 2","id":"child2Id","onClick":"child2Click"}]},{"title":"Children 2","child":[{"title":"Child 3","id":"child3Id","onClick":"child3Click"},{"title":"Child 4","id":"child4Id","onClick":"child4Click"}]}]},{"id":"title2","title":"Title 2","privilege":"","children":[{"title":"Children 3","privilege":"","child":[{"title":"Child 5","id":"child5Id","onClick":"child3Click","url":"/child5","privilege":""},{"title":"Child 6","id":"child6Id","onClick":"child6Click","url":"/child6","privilege":""}]},{"title":"Children 4","privilege":"","child":[{"title":"Child 7","id":"child7Id","onClick":"child7Click","url":"/child7","privilege":""},{"title":"Child 8","id":"child8Id","onClick":"child8Click","url":"/child8","privilege":""}]}]}]
const routeStates = fn(headers)
console.log(routeStates)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
try to access it in a loop and to access it try this as it is an object...
for ( let i = 0; i < headers.length; i++ ) {
for (let j = 0; j < headers[i].children.length; j++ ) {
for (let k = 0; k < headers[i].children[j].child.length; j++ ) {
console.log( headers[i].children[j].child[k].onClick);
}
}
}
it will print as follows ...
child1Click,
child2Click,
child3Click,
child4Click,
child5Click,
child6Click,
child7Click,
child8Click
if you want to set the all childClick to null try adding this..
console.log( headers[i].children[j].child[k].onClick: null);
i hope it helps
I borrowed a solution I found here to create this:
var headers = [
//...
];
var newObj = {};
function iterate(obj) {
Object.keys(obj).forEach(key => {
if (key === 'onClick') {
newObj[obj[key]] = 'null';
}
if (typeof obj[key] === 'object') {
iterate(obj[key])
}
})
}
iterate(headers);
console.log(newObj);
Related
I have an array of objects which is nested. How do I make an array of objects with this one getting values from nested properties as well? If onClick property is empty, it means there is a property called children and the parent element should not be a part of the new list. I need to loop through the children array and get the values. Please see the expected output below.
const headers = [{
title: 'Arun',
id: 'arunId',
onClick: 'onClickArun'
},
{
title: "George",
id: 'georgeId',
onClick: '',
children: [{
title: 'David',
id: 'davidId',
onClick: 'onClickDavid'
},
{
title: 'Patrick',
id: 'patrickId',
onClick: 'onClickPatrick'
}
]
},
{
title: 'Mark',
id: 'markId',
onClick: 'onClickMark'
}
];
console.log(headers.map(item => {
return {
title: item.title,
onClick: item.onClick
}
}))
Expected Output:
[{
title: 'Arun',
onClick: 'onClickArun'
},
{
title: 'David',
onClick: 'onClickDavid'
},
{
title: 'Patrick',
onClick: 'onClickPatrick'
},
{
title: 'Mark',
onClick: 'onClickMark'
}
]
Any help is greatly appreciated.
You could take Array#flatMap with a recursive callback.
const
map = ({ title, onClick, children }) => onClick
? { title, onClick }
: children.map(map);
var headers = [{ title: 'Arun', id: 'arunId', onClick: 'onClickArun' }, { title: "George", id: 'georgeId', onClick: '', children: [{ title: 'David', id: 'davidId', onClick: 'onClickDavid' }, { title: 'Patrick', id: 'patrickId', onClick: 'onClickPatrick' }] }, { title: 'Mark', id: 'markId', onClick: 'onClickMark' }],
result = headers.flatMap(map);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can do this using Array.prototype.reduce, where you just need to check if the parent onClick prop is empty and the children prop is present or not:
var headers = [{ title: 'Arun', id: 'arunId', onClick: 'onClickArun' }, { title: "George", id: 'georgeId', onClick: '', children: [{ title: 'David', id: 'davidId', onClick: 'onClickDavid' }, { title: 'Patrick', id: 'patrickId', onClick: 'onClickPatrick' }] }, { title: 'Mark', id: 'markId', onClick: 'onClickMark' }];
function getObject(headers, acc){
return headers.reduce((acc, ele) => {
if(!ele.onClick.length && ele.children){
acc = getObject(ele.children, acc);
}else{
acc.push({"title": ele.title, "onClick": ele.onClick});
}
return acc;
}, acc);
}
console.log(getObject(headers, []));
Looks like you need a depth first search.
You run on the array, for each item with children recurse on it and pass along the existing array. otherwise, add the item to the list.
function getChildren(array, total = []){
for(let item of array) {
if(item.children) {
getChildren(item.children, total)
} else {
total.push({
title: item.title,
onClick: item.onClick
})
}
}
return total
}
let headers = [{
title: 'Arun',
id: 'arunId',
onClick: 'onClickArun'
},
{
title: "George",
id: 'georgeId',
onClick: '',
children: [{
title: 'David',
id: 'davidId',
onClick: 'onClickDavid'
},
{
title: 'Patrick',
id: 'patrickId',
onClick: 'onClickPatrick'
}
]
},
{
title: 'Mark',
id: 'markId',
onClick: 'onClickMark'
}
]
// take children, only if onClick is empty
.map(item => item.onClick ? item : item.children)
// make flat array
headers = [].concat.apply([], headers)
.map(item => {
const temp = {};
temp.title = item.title;
temp.onClick = item.onClick;
return temp; // take only onClick and title property from each item
})
I have 2 arrays of objects
NOTE: status and original-language can't be set manually as they change all the time, these are custom fields. The slugs from fields make up all custom fields.
const items = [
{
name: 'Surviving the Game',
status: 'ffdb29ba075fcbc0b71295c31a13d64f',
original-language: 'b4ebbe06702794d1cf375274197267b2',
},
{
name: 'Some Movie',
status: 'cd53c082c6ca9e7d3ec66890e66c01f3',
original-language: '7a1cac74217747933bb3915888dea090',
},
];
const fields = [
{
slug: 'status',
options: [
{
name: 'Released',
id: 'ffdb29ba075fcbc0b71295c31a13d64f',
},
{
name: 'Upcoming',
id: 'cd53c082c6ca9e7d3ec66890e66c01f3',
},
],
},
{
slug: 'original-language',
options: [
{
name: 'de',
id: 'b4ebbe06702794d1cf375274197267b2',
},
{
name: 'en',
id: '7a1cac74217747933bb3915888dea090',
},
],
},
];
status and original-language in [items] have an id value which matches an option in the corresponding fields array.
I am trying to return a new array for [items] with the name from options with the matching id.
eg:
[
{
name: 'Surviving the Game',
status: 'Released',
original-language: 'de',
},
{
name: 'Some Movie',
status: 'Upcoming',
original-language: 'en',
},
];
How would I go about this with ES6/7?
I am not sure where to start
I would accomplish this by creating a lookup object that houses lookups for both your statuses and languages. You can then use this lookup object when mapping through your items.
var items = [
{
name: 'Surviving the Game',
status: 'ffdb29ba075fcbc0b71295c31a13d64f',
"original-language": 'b4ebbe06702794d1cf375274197267b2'
},
{
name: 'Some Movie',
status: 'cd53c082c6ca9e7d3ec66890e66c01f3',
"original-language": '7a1cac74217747933bb3915888dea090'
}
];
var fields = [
{
slug: 'status',
options: [
{
name: 'Released',
id: 'ffdb29ba075fcbc0b71295c31a13d64f'
},
{
name: 'Upcoming',
id: 'cd53c082c6ca9e7d3ec66890e66c01f3'
}
]
},
{
slug: 'original-language',
options: [
{
name: 'de',
id: 'b4ebbe06702794d1cf375274197267b2'
},
{
name: 'en',
id: '7a1cac74217747933bb3915888dea090'
}
]
}
];
const lookup = {};
fields.forEach(field => {
lookup[field.slug] = field.options.reduce((all, option) => ({
...all,
[option.id]: option.name
}), {})
});
const translatedItems = items.map(item => {
return Object.entries(item)
.reduce((all, [key, val]) => ({
...all,
[key]: lookup[key] ? lookup[key][val] : val
}),{});
});
console.log(translatedItems);
I'd define a function that obtains the value for a field, like so:
function valueForField(field, id) {
const field = fields.find((itemfields) => itemfields.slug === field);
if(!field) return null;
const option = field.options.find(option => option.id === id);
if(!option) return null;
return option.name;
}
This can then be used like so:
const newItems = items.map(item => {
const { name } = item;
const newItem = {name};
newItem["original-language"] = valueForField('original-language', item["original-language"]);
newItem.status = valueForField('status', item.status);
return newItem;
});
Use map to create a new array of objects having name, status and originalLanguage fields along with the find method to get the name from fields for every status identifier.
const items = [{
name: 'Surviving the Game',
status: 'ffdb29ba075fcbc0b71295c31a13d64f',
originalLanguage: 'b4ebbe06702794d1cf375274197267b2',
},
{
name: 'Some Movie',
status: 'cd53c082c6ca9e7d3ec66890e66c01f3',
originalLanguage: '7a1cac74217747933bb3915888dea090',
},
];
const fields = [{
slug: 'status',
options: [{
name: 'Released',
id: 'ffdb29ba075fcbc0b71295c31a13d64f',
},
{
name: 'Upcoming',
id: 'cd53c082c6ca9e7d3ec66890e66c01f3',
},
],
},
{
slug: 'original-language',
options: [{
name: 'de',
id: 'b4ebbe06702794d1cf375274197267b2',
},
{
name: 'en',
id: '7a1cac74217747933bb3915888dea090',
},
],
},
],
newArr = items.map(i => ({
name: i.name,
status: fields.find(f => f.slug == 'status').options.find(o => o.id == i.status).name,
originalLanguage: fields.find(f => f.slug == 'original-language').options.find(l => l.id == i.originalLanguage).name
}));
console.log(newArr);
I need to access properties of a JavaScript object array and convert it to a new array. The array takes a form like this.
Company = [
{
name:"A",
items:[
{ name:"AA",
items:[
{ name:"AAA",
items:[...]
},
{ name:"AAB",
items:[...]
}
]
},
{ name:"AB",items:[{},{}]}
]
},
{
name:"B", items:[{ name:"BA",items:[{...},{...}]},{ name:"BB",items:[{...},{...}]}]
},
{
name:"C", items:[{ name:"CA",items:[{...},{...}]},{ name:"CB",items:[{...},{...}]}]
}
........
];
I need to convert this to a structure like this.
Company = [
{ title: 'A',
children: [
{ title: 'AA',
children: [{ title: 'AAA',children: [..] }]
},
{ title: 'AB', children: [{ title: 'ABA',children: [..] }
]
},
{ title: 'B',
children: [
{ title: 'BA',
children: [{ title: 'BAA',children: [..] }]
},
{ title: 'BB', children: [{ title: 'BBA',children: [..] }
]
},
.........
]
I tried to use ES6 Map,Reducer,Filter and lodash to get the array converted but i couldn't. Any idea on how to achive this?. Here is what i have tried out so far
function checkitems(data){
if (typeof data.items !== "undefined" || data.items.length > 0) {
data.items.map(dept => {
return {
title: dept.name,
children: checkitems(dept)
};
});
}
return;
}
company.map((dept)=>{
return {
title:dept.name,
children: checkitems(dept)
}
});
You could recursively update the items like this:
const Company=[{name:"A",items:[{name:"AA",items:[{name:"AAA",},{name:"AAB",}]},{name:"AB"}]},{name:"B",items:[{name:"BA",items:[{name:"BAA",},{name:"BAB",}]},{name:"BB",}]},{name:"C",items:[{name:"CA",},{name:"CB",}]}];
function trasnform(array) {
return array.map(({ name , items }) => {
const obj = { title: name }
if(items)
obj.children = trasnform(items);
return obj;
})
}
console.log(trasnform(Company))
Using destructuring assignment and default arguments -
const transform = (arr = []) =>
arr.map(({ name, items = [] }) =>
({ title: name, children: transform(items) })
)
Decomposed using mutual recursion -
const transform1 = ({ name, items = [] }) =>
({ title: name, children: transform(items) })
const transform = (arr = []) =>
arr.map(transform1)
I've got multiple items, some of them with the same title. I want to create an multidimensional array with the title as the first key and a unique number as the second key. So it's possible to categorize the items by title.
Example:
itemArray['title1'][0] = item1
itemArray['title1'][1] = item2
itemArray['title2'][0] = item3
My example is working with this code, but in my opinion it's to complicated and I hope there is an other way with JavaScript.
let itemArray = {}
items.forEach(item => {
let title = item['title']
if (itemArray[title] == null) {
itemArray[title] = {}
}
let index = Object.keys(itemArray[title]).length
itemArray[title][index] = item
},
)
The Input:
[ RowDataPacket {
uid: 1,
title: 'booktitle',
description: 'description' },
RowDataPacket {
uid: 2,
title: 'booktitle',
description: 'description 2' },
RowDataPacket {
uid: 1,
title: 'booktitle 2',
description: 'description' } ]
Expected output (Since it's the result of a sql query the item is a RowDataPacket):
{ 'booktitle':
{ '0':
RowDataPacket {
uid: 1,
title: 'booktitle',
description: 'description' } },
{ '1':
RowDataPacket {
uid: 2,
title: 'booktitle',
description: 'description 2' } },
'booktitle 2':
{ '0':
RowDataPacket {
uid: 1,
title: 'booktitle 2',
description: 'description' } }
}
It's easy with PHP, there it's working like this:
$itemArray = array();
foreach ($items as $key => $item) {
$itemArray[$item['title']][] = $item;
}
Thanks in advance!
You could reduce the array by taking a default array and push the item.
var items = [{ title: 'title1' }, { title: 'title1' }, { title: 'title2' }],
result = items.reduce((r, item) => {
(r[item.title] = r[item.title] || []).push(item);
return r;
}, {});
console.log(result);
You've got the correct idea. The itemArray you created is not a multidimensional array. It's an object with each title as key and an array of items which share the same title as their value. You could probably simplify your forEach like this:
var items = [{
uid: 1,
title: 'booktitle',
description: 'description'
}, {
uid: 2,
title: 'booktitle',
description: 'description 2'
}, {
uid: 1,
title: 'booktitle 2',
description: 'description'
}]
let itemArray = {}
items.forEach(item => {
itemArray[item.title] = itemArray[item.title] || [];
itemArray[item.title].push(item)
})
console.log(itemArray)
Checck if itemArray already has the title as a key. If yes, use it. Else, point it to an empty array []. Then just push the current item to that property.
With reduce, you can even simplify it to:
var items=[{uid:1,title:'booktitle',description:'description'},{uid:2,title:'booktitle',description:'description 2'},{uid:1,title:'booktitle 2',description:'description'}]
let itemArray = items.reduce((acc,i) =>
((acc[i.title] = acc[i.title] || []).push(i), acc)
,{})
console.log(itemArray)
Expected output
{ 'booktitle':
{ '0':
RowDataPacket {
uid: 1,
title: 'booktitle',
description: 'description' } },
{ '1':
RowDataPacket {
uid: 2,
title: 'booktitle',
description: 'description 2' } },
'booktitle 2':
{ '0':
RowDataPacket {
uid: 1,
title: 'booktitle 2',
description: 'description' } }
}
That's a bad practice. Don't use enumerated properties on Objects. You see how cumbersome they are. Unlike PHP, JS has no associative Arrays; so using Objects for that is the right equivalent. But for indexed Arrays you should use Arrays in JS, not Objects.
var data = [{
uid: 1,
title: 'booktitle',
description: 'description'
},
{
uid: 2,
title: 'booktitle',
description: 'description 2'
},
{
uid: 1,
title: 'booktitle 2',
description: 'description'
}
];
const booksByTitle = {};
for (const item of data) {
const {
title
} = item;
if (!(title in booksByTitle)) booksByTitle[title] = [];
booksByTitle[title].push(item);
}
console.log(booksByTitle);
.as-console-wrapper{top:0;max-height:100%!important}
but there are also plenty of frameworks in JS that offer some groupBy method, something like this quick implementation:
var data = [{
uid: 1,
title: 'booktitle',
description: 'description'
},
{
uid: 2,
title: 'booktitle',
description: 'description 2'
},
{
uid: 1,
title: 'booktitle 2',
description: 'description'
}
];
function groupBy(iterable, keyOrFn) {
const fn = typeof keyOrFn === "function" ?
keyOrFn :
(item) => item == null ? undefined : item[keyOrFn];
var result = Object.create(null);
for (const item of iterable) {
const key = fn(item);
if (key in result) {
result[key].push(item);
} else {
result[key] = [item];
}
}
return result;
}
const booksByTitle = groupBy(data, "title");
console.log(booksByTitle);
.as-console-wrapper{top:0;max-height:100%!important}
//Structure
const definitions = {
sections: [
{ title: 'Section A', actions: [ { id: 0, name: 'action A' } ] },
{ title: 'Section B', actions: [ { id: 1, name: 'action B' } ] },
]
};
//Code to retrieve the action
const id = 1;
const sectionDef = definitions.sections.find(s => s.actions.find(a => a.id === id));
const actionDef = sectionDef.actions.find(a => a.id === id);
//Print it
console.log(actionDef);
The above solution works, but I think there must be a better way to retrieve an object from inside an array especially since I need to run the same code twice...
You can do it with an Array.forEach and an Array.find
//Structure
const definitions = {
sections: [
{ title: 'Section A', actions: [ { id: 0, name: 'action A' } ] },
{ title: 'Section B', actions: [ { id: 1, name: 'action B' } ] },
]
};
//Code to retrieve the action
const id = 1;
let action;
definitions.sections.forEach(section => {
action = section.actions.find(a => a.id === id);
});
//Print it
console.log(action);
You could use a recursive approach by using all values of the object for searching in an arbitrary data structure.
function find(object, key, value) {
var result;
if (object[key] === value) {
return object;
}
if (object && typeof object === 'object') {
Object.values(object).some(o => result = find(o, key, value));
}
return result;
}
var definitions = { sections: [{ title: 'Section A', actions: [{ id: 0, name: 'action A' }] }, { title: 'Section B', actions: [{ id: 1, name: 'action B' }] }] };
console.log(find(definitions, 'id', 0));
console.log(find(definitions, 'id', 1));
Your code has an error on const sectionDef = definitions.find, as it should be const sectionDef = definitions.sections.find as find works for array type. And the way you are doing is fine to achieve the desired result.
const definitions = {
sections: [
{ title: 'Section A', actions: [ { id: 0, name: 'action A' } ] },
{ title: 'Section B', actions: [ { id: 1, name: 'action B' } ] },
]
}
const id = 1;
const sectionDef = definitions.sections.find(s => s.actions.find(a => a.id === id));
const actionDef = sectionDef.actions.find(a => a.id === id);
console.log(sectionDef);
console.log(actionDef);
const definitions = {
sections: [
{ title: 'Section A', actions: [ { id: 0, name: 'action A' } ] },
{ title: 'Section B', actions: [ { id: 1, name: 'action B' } ] },
]
}
const id = 1;
var result2;
var data = definitions.sections;
var result = data.filter(function(obj) {
var data2 = obj.actions;
result2 = data2.filter(function(obj) {
return obj.id == id;
});
});
console.log(result2);