Convert string array to multidimensional array - javascript

I have a javascript string array which I want to convert to a multidimensional array:
const maps = [
"local://aaa/bbb/ccc",
"local://aaa/bbb/ddd",
"local://aaa/bbb/eee",
"as://fff/ggg/hhh",
];
I want to convert it to this:
const maps = [
{label: "aaa", children: [
{label: "bbb", children: [
{label: "ccc", children: []},
{label: "ddd", children: []},
{label: "eee", children: []}
]}
]},
{label: "fff", children: [
{label: "ggg", children: [
{label: "hhh", children: []}
]}
]}
];
I've tried to do it like this, but it turns out that its not working correctly and I think this is also not the appropiate way to handle this:
interface DialogItem {
label: string,
children: DialogItem[]
};
const dialogs: string[] = [
"local://aaa/bbb/ccc",
"local://aaa/bbb/ddd",
"local://aaa/bbb/eee",
"as://fff/ggg/hhh",
];
const mapFolder = (dialogs: string[]) => {
const maps: DialogItem[] = [];
for (const dialog of dialogs) {
const dialogStr: string = dialog.replace(/(\w+):\/\//gi, "");
const dialogArr: string[] = dialogStr.split("/");
const parent = maps.find(mapped => mapped.label === dialogArr[0]);
if (parent === undefined) {
maps.push({label: dialogArr[0], children: []});
} else {
dialogArr.shift();
const child = parent.children.find(mapped => mapped.label === dialogArr[0]);
if (child === undefined) {
parent.children.push({label: dialogArr[0], children: []});
} else {
child.children.push({label: dialogArr[1], children: []});
}
}
}
};
mapFolder(dialogs);

You could split the string to get an array of paths. Use a mapper object to keep track of each nested object and it's path. reduce the chunks and return the nested object in each iteration
const maps = [
"local://aaa/bbb/ccc",
"local://aaa/bbb/ddd",
"local://aaa/bbb/eee",
"as://fff/ggg/hhh"
],
mapper = {},
tree = { children: [] } // root object
for (const str of maps) {
let chunks = str.split('//')[1].split("/"),
path = '';
chunks.reduce((parent, label) => {
if (path)
path += `.${label}`
else
path = label
if (!mapper[path]) {
const o = { label, children: [] };
mapper[path] = o;
parent.children.push(o)
}
return mapper[path];
}, tree)
}
console.log(tree.children)

The expected output can be obtained by using nested maps and recursively appending the data to the sub-array. Also, have handled couple of more test cases.
var dialogs = [
"local://aaa/bbb/ccc",
"local://aaa/bbb/ddd",
"local://aaa/bbb/eee",
"local://aaa/iii/jjj",
"",
"as://",
"as://fff/ggg/hhh",
];
var maps = [];
dialogs.map((dialog) => {
const dialogStr = dialog.substr(dialog.indexOf("//") + 2, dialog.length);
const dialogArr = dialogStr.split("/");
var localObj = maps;
dialogArr.map((elem) => {
if (elem.trim() != "") {
var data = localObj.find(ele => ele.label == elem);
if (data) {
localObj = data['children'];
} else {
localObj.push({
label: elem,
children: []
});
localObj = localObj.find(ele => ele.label == elem)['children'];
}
}
});
});
console.log(maps);

Related

How to transform nested object of objects into array of objects

Hi all I have the following code
the data that I want to transform.
const obj = {
numbers: {
label: "main numbers",
pageTitle: "Numbers",
key: "1",
items: {
firstNumber: {
label: "first number",
pageTitle: "first",
key: "first"
},
secondNumber: {
label: "second number",
pageTitle: "second",
key: "second"
}
}
},
letters: {
label: "main Letters",
pageTitle: "Letters",
key: "2",
items: {
firstLetter: {
label: "first Letter",
pageTitle: "first",
key: "first"
}
}
},
signs: {
label: "main sign",
pageTitle: "Sign",
key: "3"
}
};
In my obj variable I have 3 other objects
numbers object which has items property which includes 2 other objects.
letters object which has items property which includes only one object.
signs object.
I need to transform my obj to the following way.
[
{
label:"main numbers",
pageTitle:"Numbers",
key:1,
children: [{label,pageTitle,key},{label,pageTitle,key}]
},
{
label:"main Letters",
pageTitle:"Letters",
key:1,
children: [{label,pageTitle,key}]
},
{
label:"main sign",
pageTitle:"Sign",
key:1,
children: []
},
]
for that transformation, I wrote the following code.
const transformedData = Object.values(obj).map((menuitem) => menuitem);
const data = [];
transformedData?.map((x) => {
const newData = {};
newData.label = x.label;
newData.pageTitle = x.pageTitle;
newData.key = x.key;
newData.children = x?.Object?.values(items)?.map((el) => {
newData.children.label = el.label;
newData.children.pageTitle = el.pageTitle;
newData.children.key = el.key;
});
data.push(newData);
});
Everything was working, but for children instead of printing an array it prints undefined.
Please help me to resolve this issue.
I created a function for your case.
const convert = data =>
Object.values(data)?.map(x => ({
label: x.label,
pageTitle :x.pageTitle ,
key: x.pathname,
children: x.items
? Object.values(x.items || {}).map(el => ({ label: el.label,
key:el.pathname,pageTitle:el.pageTitle }))
: null,
}));
You can use like const items = convert(obj).
xdoesn't have Objects. Change it to:
newData.children = Object.values(x.items)?.map(/*...*/);
Is this what you're after?
const transformedData = Object.values(obj).map((menuitem) => menuitem);
const data = [];
transformedData?.map((x) => {
const newData = {};
newData.label = x.label;
newData.pageTitle = x.pageTitle;
newData.key = x.key;
if(x.hasOwnProperty('items')){
newData.children = Object.values(x.items).map((el) => {
const obj={
label:el.label,
pageTitle:el.pageTitle,
key:el.key
}
return obj
})};
data.push(newData);
});
console.log(data)
Your code return undefined because inside map you didn't return anything so newData.children was never populated with anything.
Also, I think accessing and assigning newData.children.label was problematic since there was no newData.children yet. So we declare a temp obj inside map and we return it
Lastly we need to check if items is a property that exists in the first place.

Retain array structure when filtering nested array

My brain froze with this advanced filtering. This task has exceeded my basic knowledge of filter, map etc.
Here I have an array with nested objects with array:
const DATA = [
{
title: 'Spongebob',
data: [
{ id: 1, name: 'Mr Crabs' },
{ id: 2, name: 'Sandy' }
]
},
{
title: 'Dragon Balls Z',
data: [
{ id: 1, name: 'GoKu' },
{ id: 2, name: 'Zamasu' }
]
}
];
You may have seen this sort of style if you've worked with React Native (RN). This question is not for RN. I need to perform a filter on the name property in the nested array and when I get a match, I must return the format as the DATA variable.
const handleFiltering = (value) => {
const _value = value.toLowerCase();
const results = DATA.map(o => {
return o.data.filter(o => o.name.toLowerCase().indexOf(_value) != -1)
});
console.log(results);
};
My limited knowledge of deep filtering returns the basic filtering for the data array but need to retain the structure for DATA. The expected results I'd expect:
// I'm now querying for "ZAMASU"
const handleFiltering = (value='ZAMA') => {
const _value = value.toLowerCase();
const results = DATA.map(o => {
return o.data.filter(o => o.name.toLowerCase().indexOf(_value) != -1)
});
// console.log(results) should now be
// [
// {
// title: 'Dragon Balls Z',
// data: [
// { id: 2, name: 'Zamasu' }
// ]
// }
// ];
};
What comes to mind is the use of {...DATA, something-here } but my brain has frozen as I need to get back the title property. How to achieve this, please?
Another solution would be first use filter to find only objects containing the name in data passed through the argument, subsequently mapping data.
Here is your adjusted filter method
const handleFiltering = (value) => {
const _value = value.toLowerCase();
const results = DATA.filter((obj) =>
obj.data.some((character) => character.name.toLowerCase() === _value)
).map((obj) => ({
title: obj.title,
data: obj.data.filter(
(character) => character.name.toLowerCase() === _value
),
}));
console.log(results);
};
You can use reduce method of array. First find out the object inside data array and then add that to accumulator array as new entry by preserving the original structure.
const DATA = [
{
title: 'Spongebob',
data: [
{ id: 1, name: 'Mr Crabs', where: 'tv' },
{ id: 2, name: 'Sandy' }
]
},
{
title: 'Dragon Balls Z',
data: [
{ id: 1, name: 'GoKu' },
{ id: 2, name: 'Zamasu' }
]
}
];
let handleFiltering = (value='tv') => {
return DATA.reduce((acc,d) => {
let obj = d.data.find(a => a.name?.toLowerCase().includes(value.toLowerCase())
|| a.where?.toLowerCase().includes(value.toLowerCase()));
obj ? acc.push({...d, data:[obj]}) : null;
return acc;
}, []);
}
let result = handleFiltering();
console.log(result);

tree from array of dot-separated strings

I have an array of dot delimited strings which looks like the following
data = [
'Europe.UK.London.TrafalgarSq',
'Europe.UK.London.HydePark',
'Europe.UK.London.OxfordStreet',
'Europe.UK.London.City.Bank',
'Europe.France.Paris',
'Europe.France.Bordeaux'},
]
and I want to build the following tree of of nested objects. In case it matters, this is for a leaflet map where the Tree Layers Control is going to be used
var tree = {
label: 'Places',
selectAllCheckbox: 'Un/select all',
children: [
{
label: 'Europe',
selectAllCheckbox: true,
children: [
{
label: 'Europe.UK',
selectAllCheckbox: true,
children: [
{
label: 'Europe.UK.London',
selectAllCheckbox: true,
children: [
{label: 'Europe.UK.London.TrafalgarSq'},
{label: 'Europe.UK.London.HydePark'},
{label: 'Europe.UK.London.OxfordStreet'},
{
label: 'Europe.UK.London.City',
selectAllCheckbox: true,
children: [
{label: 'Europe.UK.London.City.Bank'},
]
},
]
},
{
label: 'Europe.France',
selectAllCheckbox: true,
children: [
{label: 'Europe.France.Paris'},
{label: 'Europe.France.Bordeaux'},
]
},
]
}
]
}
]
};
How do I do this tree please?
You could use a mapper object which has partial paths (or label) as key and a reference to the object in the tree as it's value. split the path at . and reduce the array with tree as the initialValue. If the path doesn't exist yet, add it to mapper and tree. Return the nested object in each iteration.
const data = ["Europe.UK.London.TrafalgarSq","Europe.UK.London.HydePark","Europe.UK.London.OxfordStreet","Europe.UK.London.City.Bank","Europe.France.Paris","Europe.France.Bordeaux"],
mapper = {},
tree = {
label: 'Places',
selectAllCheckbox: 'Un/select all',
children: []
}
for (const str of data) {
let splits = str.split('.'),
label = '';
splits.reduce((parent, place) => {
if (label)
label += `.${place}`
else
label = place
if (!mapper[label]) {
const o = { label };
mapper[label] = o;
parent.selectAllCheckbox = true
parent.children = parent.children || [];
parent.children.push(o)
}
return mapper[label];
}, tree)
}
console.log(tree)
You could an iterative approach with a reduceing of the nested objects.
var data = ['Europe.UK.London.TrafalgarSq', 'Europe.UK.London.HydePark', 'Europe.UK.London.OxfordStreet', 'Europe.UK.London.City.Bank', 'Europe.France.Paris', 'Europe.France.Bordeaux'],
children = data.reduce((r, s) => {
s.split('.').reduce((q, _, i, a) => {
q.selectAllCheckbox = true;
var label = a.slice(0, i + 1).join('.'),
temp = (q.children = q.children || []).find(o => o.label === label);
if (!temp) q.children.push(temp = { label });
return temp;
}, r);
return r;
}, { children: [] }).children,
tree = { label: 'Places', selectAllCheckbox: 'Un/select all', children };
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }

JS array of arrays to object

I have a JS array (shown 4 examples actual has 66 )
[["A","Example1"],["A","Example2"],["B","Example3"],["B","Example4"]]
that I am trying to get into an object for a multi select drop down menu:
var opt = [{
label: 'A', children:[
{"label":"Example1","value":"Example1","selected":"TRUE"},
{"label":"Example2","value":"Example2","selected":"TRUE"}
]
},
{
label: 'B', children:[
{"label":"Example3","value":"Example3","selected":"TRUE"},
{"label":"Example4","value":"Example4","selected":"TRUE"}
]
}
]
Is there a easy way to do this ?
Updated:
Using reduce() and filter() to get expected results.
const result = [['A', 'Example1'], ['A', 'Example2'], ['B', 'Example3'], ['B', 'Example4']].reduce((acc, cur) => {
const objFromAccumulator = acc.filter((row) => row.label === cur[0]);
const newChild = {label: cur[1], value: cur[1], selected: 'TRUE'};
if (objFromAccumulator.length) {
objFromAccumulator[0].children.push(newChild);
} else {
acc.push({label: cur[0], children: [newChild]});
}
return acc;
}, []);
console.log(result);
Something like this should work:
const raw = [["A","Example1"],["A","Example2"],["B","Example3"],["B","Example4"]];
const seen = new Map();
const processed = raw.reduce((arr, [key, label]) => {
if (!seen.has(key)) {
const item = {
label: key,
children: []
};
seen.set(key, item);
arr.push(item);
}
seen.get(key).children.push({
label,
value: label,
selected: "TRUE"
})
return arr;
}, []);
console.log(processed);
Here's a rather efficient and concise take on the problem using an object as a map:
const data = [["A","Example1"],["A","Example2"],["B","Example3"],["B","Example4"]];
const opt = data.reduce((results,[key,val]) => {
if(!results[0][key]) //first element of results is lookup map of other elements
results.push(results[0][key] = { label: key, children: [] });
results[0][key].children.push({ label: val, value: val, selected:"TRUE" });
return results;
}, [{}]).slice(1); //slice off map as it's no longer needed
console.log(opt);

javascript array of string to deep merged object

I am trying to convert an array of strings (with many more items):
fullRoutes = ['POST /api/v1/user/login','POST /api/v1/user/logout']
Into a deep nested object like this (to use in the following module react-checkbox-tree):
const nodes = [{
value: 'api',
label: 'api',
children: [
{ value: 'v1',
label: 'v1',
children: [
{ value: 'user',
label: 'user',
children: [
{ value: login, label: login},
{ value: logout, label: logout}
]
}
]
}
]
I managed to get to:
fullRoutes.forEach(function(route){
let path = route.split(" ")[1].split("/").filter(function(e){ return e === 0 || e })
let object = {}
path.reduce(function(o, s) {
return o['children'] = {label: s, value: s, children: []}
}, object)
routes.push(object)
})
Which returns the object with the 'children', but I am struggling to merge them correctly
I believe this will work:
fullRoutes = [
'POST /api/v1/user/login',
'POST /api/v1/user/logout',
'POST /api/v2/user/login'
];
routes = [];
fullRoutes.forEach(route => {
let path = route.split(' ')[1].split('/').filter(e => e);
let rs = routes;
for (let i = 0, n = path.length; i < n; i++) {
let seg = path[i];
let segp = path.slice(0, i + 1).join('/');
let node = rs.find(r => r.label == seg);
if (!node)
rs.push(node = {
label: seg,
value: segp,
children: []
});
rs = node.children;
}
});
console.log(routes);
One way is to reduce everything to an object including the children and use the path name as key within the children
Then recursively loop through all children and use Object#values() to convert them from objects to arrays
const fullRoutes = ['POST /api/v1/user/login', 'POST /api/v1/user/logout'];
const tmp = fullRoutes.reduce(function(tmp, route){
let path = route.split(" ")[1].split("/");
path.reduce(function(o, s, i) {
o[s] = o[s] || {label: s, value: s, children: {}};
return o[s].children;
}, tmp);
return tmp;
},{});
const nodes = Object.values(tmp);
nodes.forEach(childrenToArray);
console.log(nodes)
//recursive helper
function childrenToArray(obj) {
obj.children = Object.values(obj.children);
obj.children.forEach(childrenToArray)
}
.as-console-wrapper {max-height: 100%!important;}

Categories