In plain javascript, I am trying to create a function that will return a tree structure (json) of a folder, its subfolders and any files. I'm trying to achieve this using recursion.
The problem with the below code is that it stops after the first recursive call.
I know that in JS you do references, and I need to create a new object that I pass the values from the previous call to, but I am struggling to do so.
function fun(file, json) {
var tempJson = {
'name' : json.name || '',
'children' : obj.children || new Object()
};
if (file.type == 'file') {
tempJson.type = 'file';
tempJson.children = {}; // this will be empty, since there are no children
}
else {
tempJson.type = 'dir';
var listed = file.listFiles();
if (listed.length > 0) {
for each (var item in listed) {
tempJson.children = fun(item, tempJson);
}
} else {
tempJson.children = {};
}
}
return tempJson;
}
Example
From a directory structure like:
-root
--file1
--dir1
---file1.1
--dir2
I would like to get a json like:
{
name: 'root',
type: 'dir',
children : [
{
name: 'file1',
type: 'file',
children: {}
},
{
name: 'dir1',
type: 'dir',
children:
{
name: 'file1.1',
type: 'file',
children: {},
}
},
name: 'dir2',
type: 'dir',
children: {}
}
First call:
var object = new Object();
fun(rootdir, object);
Hope this makes sense.
Thanks!
As pointed out in the comments, children should be an array:
function fun(entry) {
var entryObj = { // construct the object for this entry
name: entry.name || "",
type: entry.type, // put the type here instead of using an if
children: [] // children must be an array
};
if(entry.type === "dir") { // if this entry is a directory
var childEntries = entry.listFiles(); // get its child entries
for(var childEntry of childEntries) { // and for each one of them
entryObj.children.push(fun(childEntry)); // add the result of the call of 'fun' on them to the children array
}
}
return entryObj;
}
Then call it like so:
var tree = fun(rootEntry);
Related
I'm trying to create a component that will render elements inside VueJs virtual dom with a Vuex state.
The problem is I get this error but I don't understand why and how to fix it
Avoid using observed data object as vnode data: {"class":"btn btn-default"}
Always create fresh vnode data objects in each render!
Inside my Vuex state I store and object where I define the elements properties
{
type: 'a',
config: {
class: 'btn btn-default',
},
nestedElements: [
{
type: 'span',
value: 'test',
},
{
type: 'i',
},
],
},
My components code look like
methods: {
iterateThroughObject(object, createElement, isNestedElement = false) {
const generatedElement = [],
nestedElements = [];
let parentElementConfig = {};
for (const entry of object) {
let nodeConfig = {};
if (typeof entry.config !== 'undefined' && isNestedElement) {
nodeConfig = entry.config;
} else if (typeof entry.config !== 'undefined') {
parentElementConfig = entry.config;
}
if (entry.nestedElements) {
nestedElements.push(this.iterateThroughObject(entry.nestedElements, createElement, true));
}
if (!isNestedElement) {
nodeConfig = parentElementConfig;
}
generatedElement.push(createElement(
entry.type,
nodeConfig === {} ? entry.value : nodeConfig,
nestedElements
));
}
if (isNestedElement) {
return generatedElement;
}
return createElement('ul', generatedElement);
},
},
render(createElement) {
const barToolsElements = this.$store.state.titleBar.barToolsElements;
if (barToolsElements) {
return this.iterateThroughObject(barToolsElements, createElement);
}
return false;
},
The error is produced when I try to pass inside my last generatedElement.push() definition.
Because entry.value is {"class":"btn btn-default"}.
I don't understand why it tell me to recreate a fresh Vnode object while this value is used only once.
Did I miss or misunderstand something?
Might be because you're passing references to objects in your store's state, which might lead inadvertently to their mutation. Try creating deep clones of these objects when you pass them around, like for example ..
nodeConfig = JSON.parse(JSON.stringify(entry.config));
parentElementConfig = JSON.parse(JSON.stringify(entry.config));
nodeConfig === {} ? JSON.parse(JSON.stringify(entry.value)) : nodeConfig,
I currently have this object:
var obj = {
1: {
title: 'test',
children: {
2: {
title: 'test2',
children: {}
},
3: {
title: 'test3',
children: {}
}
}
}
};
The whole idea is I make a function to add an item to this object. As parameter I send the parent.
Now, I was wondering how I would get the right item object. For example if I send parent '2', it would get 2: from the children of 1:. The only way I can think of is a for loop, but I don't know if there's a more efficient way. The children can be extended even more, so a parent has children, those children have children endlessly. That's the whole idea at least.
I think with a few items a for loop is okay, but I think if I have over 50 items it's already slow, and it'll even be slower with more.
This solution use Object.keys() for getting all keys of the given object and an array iteration with short ciruit Array.prototype.some() looks for the key. If found the reference is returned, otherwise the item is checked for an object. If so the object reference is taken for a new search with getReference().
var obj = { 1: { title: 'test', children: { 2: { title: 'test2', children: {} }, 3: { title: 'test3', children: {} } } } };
function getReference(o, p) {
var r;
Object.keys(o).some(function (k) {
if (k === p) {
r = o[k];
return true;
}
if (typeof o[k] === 'object') {
r = getReference(o[k], p);
return !!r;
}
});
return r;
}
var x = getReference(obj, '2');
document.write(x.title);
If you want adding to be fast, you can preserve indexes of your child nodes in object or map (ES6). It could look like this:
function Tree() {
this.data = {};
this.indexes = {0: this.data};
}
Tree.prototype = {
addNode: function(parentIndex, index, node) {
// handle cases when parentIndex does not exist
// handle cases when index already exists
this.indexes[index] = node;
var parent = this.indexes[parentIndex];
parent.children = parent.children || {};
parent.children[index] = node;
}
}
var tree = new Tree();
tree.addNode(0, 1, { title: 'test' });
tree.addNode(1, 2, { title: 'test2' });
tree.addNode(1, 3, { title: 'test3' });
console.log(tree.data);
I've got a flat JavaScript object like this:
{
id: 3726492,
kind: 'user',
permalink: 'nicholas',
username: 'Nicholas',
...
a lot more attributes
}
I'd like to create a new object which only has a subset of the attributes of the original object.
Something like
var newObject = oldObject.fields(['id', 'username']);
newObject would be
{
id: 3726492,
username: 'Nicholas'
}
Is there already something like this?
Try this
function pick(data, keys) {
var result = {};
keys.forEach(function (key) {
if (data.hasOwnProperty(key)) {
result[key] = data[key];
}
});
return result;
}
var data = {
id: 3726492,
kind: 'user',
permalink: 'nicholas',
username: 'Nicholas'
}
var newData = pick(data, ['id', 'kind']);
console.log(newData);
In underscorejs or lodash there is method .pick
var data = {
id: 3726492,
kind: 'user',
permalink: 'nicholas',
username: 'Nicholas',
};
var newObject = _.pick(data, 'id', 'username');
console.log(newObject);
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore.js"></script>
You can use Array.prototype.reduce to reduce one object to another using the list of properties:
function subset(obj, propList) {
return propList.reduce(function(newObj, prop) {
obj.hasOwnProperty(prop) && (newObj[prop] = obj[prop]);
return newObj;
}, {});
}
var obj = {
id: 3726492,
kind: 'user',
permalink: 'nicholas',
username: 'Nicholas'
};
var newObj = subset(obj, ['id', 'username']);
console.log(newObj);
document.getElementById('json').innerText = JSON.stringify(newObj);
<pre id="json"></pre>
Not built-in, but you can sure define a simple function that does the job:
var original = {a:1112, b:434, c:666, d:222};
function fieldSubset(obj, fields) {
var subsetClone = {};
for( var i=0,l=fields.length; i<l; i++) {
// This can prevent filling undefined as properties
if(obj.hasOwnProperty(fields[i])) {
subsetClone[fields[i]] = obj[fields[i]];
}
}
return subsetClone;
}
fieldSubset(original, ["a", "c"]);
You can also use this in Object.prototype, but be aware that this might happen to conflict with native API in the future versions of JavaScript:
var original = {a:1112, b:434, c:666, d:222};
Object.defineProperty(Object.prototype, "fieldSubset", {
value: function(fields) {
var subsetClone = {};
for( var i=0,l=fields.length; i<l; i++) {
// This can prevent filling undefined as properties
if(this.hasOwnProperty(fields[i])) {
subsetClone[fields[i]] = this[fields[i]];
}
}
return subsetClone;
},
enumerable: false,
configurable: true}
);
original.fieldSubset(["a", "c"]);
One liner using Array.prototype.reduce. We are also using Object.assign. The idea is to keep extending a blank object with the keys found in the filters array. If you see, the reduce function takes a callback function with arg1,arg2,arg3 params as the first argument and an empty object as the second argument. This object will be cloned and extended with the help of the keys specified in the filters array.
var a = {
id: 3726492,
kind: 'user',
permalink: 'nicholas',
username: 'Nicholas',
};
var filters = ["id","username","permalink"];
var sub = Object.keys(a).reduce((arg1,arg2,arg3)=>{ var res = {}; if(filters.indexOf(arg2)>=0){ res[arg2] = a[arg2]; } return Object.assign(arg1,res);},{})
console.log(sub);
You haven't specifically mentioned what is the type of values behind your object's keys. Your current answers cover the shallow copy and deep copy.
Another alternative would be to create a view of the original object. This would be helpful if you have very large data objects and you do not want them copy in the memory.
function View(obj, properties) {
var view = {};
properties.forEach(function(prop) {
Object.defineProperty(view, prop, {
get: function() {
return obj[prop];
},
set: function(val) {
obj[prop] = val;
},
enumerable: true,
configurable: true
});
});
return view;
}
then with your data you can do:
var data = {
id: 3726492,
kind: 'user',
permalink: 'nicholas',
username: 'Nicholas',
},
view = new View(data, ['id', 'username']);
view.id; // 3736492
view.username; // Nicholas
of course you have to be aware that you can change your original object just by view.id = 'something else'. However it is easily preventable.
I receive (in my angularjs application) from a server a list of directories like this:
['.trash-user',
'cats',
'cats/css',
'cats/images/blog',
'cats/images/gallery']
And I would like to build a javascript variable which looks like this:
[{
label: '.trash-user'},
{label: 'cats',
children: [{
label: 'css'},
{label: 'images',
children: [{
label: 'blog'},
{label: 'gallery'}
]}
]}
}]
The paths are in random order.
Hope somebody has some really elegant solution, but any solution is appreciated!
Edit:
Here is my naive approach, I have real trouble with recursion.
I could only make level 0 to work:
var generateTree = function(filetree){
console.log('--------- filetree -------');
var model = [];
var paths = [];
for(var i=0;i<filetree.length;i++) {
paths = filetree[i].split('/');
for(var j=0;j<paths.length;++j) {
var property = false;
for(var k=0;k<model.length;++k) {
if (model[k].hasOwnProperty('label') &&
model[k].label === paths[0]) {
property = true;
}
}
if (!property) {
model.push({label: paths[0]});
}
}
}
console.log(model);
};
If you want an elegant solution, lets start with a more elegant output:
{
'.trash-user': {},
'cats': {
'css': {},
'images': {
'blog': {},
'gallery': {},
},
},
}
Objects are much better than arrays for storing unique keys and much faster too (order 1 instead of order n). To get the above output, do:
var obj = {};
src.forEach(p => p.split('/').reduce((o,name) => o[name] = o[name] || {}, obj));
or in pre-ES6 JavaScript:
var obj = {};
src.forEach(function(p) {
return p.split('/').reduce(function(o,name) {
return o[name] = o[name] || {};
}, obj);
});
Now you have a natural object tree which can easily be mapped to anything you want. For your desired output, do:
var convert = obj => Object.keys(obj).map(key => Object.keys(obj[key]).length?
{ label: key, children: convert(obj[key]) } : { label: key });
var arr = convert(obj);
or in pre-ES6 JavaScript:
function convert(obj) {
return Object.keys(obj).map(function(key) {
return Object.keys(obj[key]).length?
{ label: key, children: convert(obj[key])} : { label: key };
});
}
var arr = convert(obj);
I'll venture that generating the natural tree first and then converting to the array will scale better than any algorithm working on arrays directly, because of the faster look-up and the natural impedance match between objects and file trees.
JSFiddles: ES6 (e.g. Firefox), non-ES6.
Something like this should work:
function pathsToObject(paths) {
var result = [ ];
// Iterate through the original list, spliting up each path
// and passing it to our recursive processing function
paths.forEach(function(path) {
path = path.split('/');
buildFromSegments(result, path);
});
return result;
// Processes each path recursively, one segment at a time
function buildFromSegments(scope, pathSegments) {
// Remove the first segment from the path
var current = pathSegments.shift();
// See if that segment already exists in the current scope
var found = findInScope(scope, current);
// If we did not find a match, create the new object for
// this path segment
if (! found) {
scope.push(found = {
label: current
});
}
// If there are still path segments left, we need to create
// a children array (if we haven't already) and recurse further
if (pathSegments.length) {
found.children = found.children || [ ];
buildFromSegments(found.children, pathSegments);
}
}
// Attempts to find a ptah segment in the current scope
function findInScope(scope, find) {
for (var i = 0; i < scope.length; i++) {
if (scope[i].label === find) {
return scope[i];
}
}
}
}
I am trying to add an object to JavaScript array and then loop it.But loop is not running
var basicConf = {
RootUrl: "https://api.joltcomm.com/",
username: 'test',
password: 'test',
APPURL:'https://dev.joltcomm.com/secureadmin/',
PROJECTID:'',
uData:'',
iFunc:[],
pushData:function(data){
this.iFunc.push(data);
}
};
function onProjectLoad(fn,params)
{
basicConf.pushData({'function':fn,'parameters':params});
}
onProjectLoad("getData",["user-project/2","showProjects",1]);
$.each(basicConf.iFunc, function( index, value ) {
console.log(value);
var fn=value.function+'(';
$.each(value.parameters, function( i, v ) {
if(i>0)
{
fn+=',';
}
fn+='"'+v+'"';
});
fn+=');';
console.log(fn);
eval(fn);
});
I am not getting into the loop I don’t know why
Well it is hard to push items to an object.
iFunc:{}, <-- object
you want an array if you actually want to add them to an array.
iFunc:[], //<-- That is an array, you can push to the array
If you just want to update the object, than do not use push.
var basicConf = {
RootUrl: "https://api.example.com/",
username: 'test',
password: 'test',
APPURL:'https://dev.example.com/example/',
PROJECTID:'',
uData:'',
iFunc:[],
pushData:function(data){
this.iFunc.push(data);
console.log(this.iFunc)
}
};
function onProjectLoad(fn,params)
{
basicConf.pushData({'function':fn,'parameters':params});
}
onProjectLoad("getData",["user-project/2","showProjects",1]);
push is an array method, you can't 'push' new things into an object as you need to provide a key to store the data against.
addData:function(data){
this.iFunc.someKey = data;
}
indeed you can't push key/val on an object directly but you can set them in an array-way approach:
var basicConf = {
RootUrl: "https://api.example.com/",
username: 'test',
password: 'test',
APPURL:'https://dev.example.com/example/',
PROJECTID:'',
uData:'',
iFunc:{},
pushData:function(data){
for (key in data) {
this.iFunc[key] = data[key];
}
}
};
function onProjectLoad(fn,params)
{
basicConf.pushData({'function':fn,'parameters':params});
}
onProjectLoad("getData",["user-project/2","showProjects",1]);
so if you console.log your basicConf, it will be like:
iFunc: Object
function: "getData"
parameters: Array[3]
0: "user-project/2"
1: "showProjects"
2: 1
I think this is what you need. A method to extend an existing object with additional values.
var basicConf = {
RootUrl: "https://api.example.com/",
username: 'test',
password: 'test',
APPURL: 'https://dev.example.com/example/',
PROJECTID: '',
uData: '',
iFunc: {},
pushData: function(data) {
this.iFunc.push(data);
}
};
function onProjectLoad(fn, params) {
extend(basicConf, {
'function': fn,
'parameters': params
});
}
onProjectLoad("getData", ["user-project/2", "showProjects", 1]);
function extend(dest, src) {
var keys = Object.keys(src);
var i = 0;
while (i < keys.length) {
if (!this.extend || (this.extend && dest[keys[i]] === undefined)) {
dest[keys[i]] = src[keys[i]];
}
i++;
}
return dest;
}