Reputation: 3313
I am doing this on the client with Javascript. I want to transform:
[
{
"id": 10,
"name": "Designer",
"slug": "designer",
"children": [
{
"id": 11,
"name": "UI / Visual Designer",
"slug": "ui-visual-designer",
"children": []
},
...
]
},
{
"id": 1,
"name": "Software Engineer",
"slug": "software-engineer",
"children": [
{
"id": 2,
"name": "Back-End Developer",
"slug": "back-end-developer",
"children": []
},
...
]
},
...
]
into this:
[
{
"id": 10,
"text": "Designer"
},
{
"id": 11,
"text": "UI / Visual Designer",
},
{
"id": 1,
"text": "Software Engineer",
},
{
"id": 2,
"text": "Back-End Developer",
}
...
]
I am practicing with map
and reduce
so I am trying to avoid for
loops (first thing I did). This is the current code I have:
var jobNewPage = {
...
buildArrayForSelect(array) {
"use strict";
return $.extend(true, [], array).reduce(function(total, item) {
if ( item.slug == 'remote' ) return total;
total.push({
'id' : item.id,
'text' : item.name
});
let children = item.children;
if (children && children.length) {
// TODO: We had to call the global context jobNewPage
total = total.concat(jobNewPage.buildArrayForSelect(children));
}
return total;
}, []);
},
...
}
So, as you can see, I have had to call jobNewPage.buildArrayForSelect(children)
to do it recursively. I tried to call this.buildArrayForSelect(children)
but the context is different. I feel this is not the best option because I don't want to depend calling a global variable inside a function in the object. How can I improve it?
Upvotes: 9
Views: 6191
Reputation: 4469
Try this:
Array.prototype.flatten = function () {
return this.reduce(function (acc, value) {
acc.push(value);
acc = acc.concat(value.children.flatten());
return acc;
}, []);
};
Array.prototype.extractData = function () {
return this.map(function(a) {
return (a.slug!='remote')?{'id':a.id,'text':a.name}:false
}).filter(function(a) {
return (a!=false)?a:false;
});
};
To extract the data:
options=array.flatten().extractData();
I've just realized that my answer follows a similar approach as James Thorpe's answer (especially the solution suggested by dfsq in a comment). However, his implementation seems far more efficient than mine.
Upvotes: 1
Reputation: 32212
It seems your question boils down to how to recursively call a function from within itself, when that function is defined using a function expression and assigned to a property on a higher-scoped object.
The simple answer is to turn it into a named function expression. Such functions are are able to call themselves recursively:
var obj = {
myMethod: function myName(n) { //function expression has name "myName"...
console.log(n);
if (n > 0)
myName(n-1); //...which we can use inside the function...
}
}
//...and outside we refer to the object's property name
obj.myMethod(5);
This approach, applied to your object and function, would look as follows:
var jobNewPage = {
//give the function expression a name:
buildArrayForSelect: function buildArrayForSelect(array) {
"use strict";
return $.extend(true, [], array).reduce(function(total, item) {
if ( item.slug == 'remote' ) return total;
total.push({
'id' : item.id,
'text' : item.name
});
let children = item.children;
if (children && children.length) {
//No need to reference object to call the function recursively:
total = total.concat(buildArrayForSelect(children));
}
return total;
}, []);
}
}
Upvotes: 5
Reputation: 386680
Example how to use Array.prototype.reduce
in combination with Array.prototype.concat
in a recursive way.
var data = [{ "id": 10, "name": "Designer", "slug": "designer", "children": [{ "id": 11, "name": "UI / Visual Designer", "slug": "ui-visual-designer", "children": [] }] }, { "id": 1, "name": "Software Engineer", "slug": "software-engineer", "children": [{ "id": 2, "name": "Back-End Developer", "slug": "back-end-developer", "children": [] }] }];
function getAll(array) {
return array.reduce(function (r, a) {
r.push({ id: a.id, text: a.name });
if (a.children && Array.isArray(a.children)) {
r = r.concat(getAll(a.children));
}
return r;
}, []);
}
document.write('<pre>' + JSON.stringify(getAll(data), 0, 4) + '</pre>');
Upvotes: 3
Reputation: 2733
You could do it as an Immediately Invoked Function Expression. Assuming your tree structure is in the variable "tree";
var tree = []; //your original tree stucture
var result = (function(input) {
var flattened = [];
var flattener = function(collection) {
collection.forEach(function(item) {
flattened.push({id: item.id, text: item.name});
if (item.children.length > 0) {
flattener(item.children);
}
});
}
flattener(input);
return flattened;
})(tree);
console.log(result);
Upvotes: 0