SOLUTION
Thanks to Dave's elegeant solution and answer below here is the solution. Side note: fwiw the additional insight or homework like what Dave provided below is very valuable for noobs. Helps us stretch.
This code will walk an existing JSON tree is for example you wanted to parse each value for whatever reason. It doesn't build but walks. In my case I'm walking and parsing each comment to a richer class:
var db = [], instance = {}, commentCounter = [];
function hydrateComments(items, parent) {
_.forEach(items, function(item) {
_.has(item, 'descendants') ? hydrateComments(item.descendants, item) : 0;
instance = new CommentModel(_.omit(item,['descendants']));
// other parsers go here, example the counter for each level
// parseCounter(comment);
(parent['children'] = (parent['children'] || [])) && item.depth > 0 ?
parent.children.push(instance) :
parent.push(instance);
});
}
hydrateComments(comments, storeComments);
ANGULAR DIRECTIVE
For those who use this code for building a tree I'm including a directive that can help you build a tree using the above mentioned tree.
Please note I've remove a lot of my own code and have not tested this, but I know I spent a ton of time trying to find both the tree and template so hopefully this helps you.
buildTree.$inject = [];
function buildTree() {
link.$inject = ["scope", "elem", "attrs"];
function link(scope, elem, attrs) {
}
CommentController.$inject = ["$scope"];
function CommentController($scope) {
$scope.$watchCollection(function () {
return CommentDataService.getComments();
},
function (newComments, oldValue) {
if (newComments) {
$scope.comments.model = newComments;
}
}, true);
}
return {
restrict: "A",
scope: {
parent: "=cmoParent"
},
template: [
"<div>",
"<script type='text/ng-template'",
"id=" + '"' + "{[{ parent.app_id }]}" + '"' + " >",
"<div class='comment-post-column--content'>",
"<div cmo-comment-post",
"post-article=parent",
"post-comment=comment>",
"</div>",
"</div>",
"<ul ng-if='comment.children'>",
"<li class='comment-post-column--content'",
"ng-include=",
"'" + '"' + "{[{ parent.app_id }]}" + '"' + "'",
"ng-repeat='comment in comment.children",
"track by comment.app_id'>",
"</li>",
"</ul>",
"</script>",
"<ul class='conversation__timeline'>",
"<li class='conversation__post-container'",
"ng-include=",
"'" + '"' + "{[{ parent.app_id }]}" + '"' + "'",
"ng-repeat='comment in comments.model[parent.app_id]",
"track by comment.app_id'>",
"</li>",
"<ul>",
"</div>"
].join(' '),
controller: CommentController,
link: link
}
}
BONUS
I also discover a great trick. How to initialize and populate an array with one line of code. In my case I have a counter method that will count each comment at each level where I've used the tip:
parseCounter: function(comment) {
var depth = comment.depth;
(commentCounter[depth] = (commentCounter[depth] || [])) ? commentCounter[depth]++ : 0;
},
ORIGINAL QUESTION
The code below parses a multi-level array of objects with the purpose of parsing all objects to instances of “CommentModel”, which although simple in this example is much richer object class, but for brevity sake I’m simplified the object/class.
EXISTING STACK EXCHANGE CONTENT:
There is a ton of content on setting multi-dimensional arrays and almost all show the examples such as:
var item[‘level1’][‘level2’] = ‘value’;
or
var item = [];
var item['level1'] = [];
or
var item = new Array([]) // and or objects
but, no examples of something like this:
var item[‘level1’].push(object)
QUESTIONS:
Is there way to initialize a 2 level deep multi-dimensional array and at the same time push to it in one line of code?
1.1 i.e. in my example below of
parent[‘children’]I’m forced to check if it exists and if not set it. If I attemptparent[‘children’].push(instance)I obviously get a push on undefined exception. Is there a one liner or a better way to check if property exists and if not? I obviously cannot just set an empty array on parent on every iteration i.e. parent[‘children’] = []; and parent[‘children’] = value wont workIs it possible to move the initialize and validation to the CommentModel instance? I ask as I attempted to
CommentModel.prototype['children'] = []; but then all child ('descendants') objects are added to every object in a proto property called “children”, which makes sense.side question - I think my tree iteration code
function hydrateComments(items, parent)is concise but is there anything I can do to streamline further with lodash and/or angular? Most example I've seen tend to be verbose and don't really walk the branches.
PLUNKER & CODE
https://plnkr.co/edit/iXnezOplN4hNez14r5Tt?p=preview
var comments = [
{
id: 1,
depth: 0,
subject: 'Subject one'
},
{
id: 2,
depth: 0,
subject: 'Subject two',
descendants: [
{
id: 3,
depth: 1,
subject: 'Subject two dot one'
},
{
id: 4,
depth: 1,
subject: 'Subject two dot two'
}
]
},
{
id: 5,
depth: 0,
subject: 'Subject three',
descendants: [
{
id: 6,
depth: 1,
subject: 'Subject three dot one'
},
{
id: 7,
depth: 1,
subject: 'Subject three dot two',
descendants: [
{
id: 8,
depth: 2,
subject: 'Subject three dot two dot one'
},
{
id: 9,
depth: 2,
subject: 'Subject three dot two dot two'
}
]
}
]
}
];
function hydrateComments(items, parent) {
_.forEach(items, function (item) {
// create instance of CommentModel form comment. Simply example
var instance = new CommentModel(item);
// if we have descendants then injec the descendants array along with the
// current comment object as we will use the instance as the "relative parent"
if (_.has(instance, 'descendants')) {
hydrateComments(instance.descendants, instance);
}
// we check is parent has a property of children, if not, we set it
// NOTE : 3 lines of code ? is there a more concise approach
if (!_.has(parent, 'children')) {
parent['children'] = [];
}
// if depth id greater than 0, we push all instances of CommentModel of that depth to the
// parent object property 'children'. If depth is 0, we push to root of array
if (item.depth > 0) {
parent.children.push(instance);
} else {
parent.push(instance);
}
})
}
// simple example, but lets assume much richer class / object
function CommentModel(comment) {
this.id = comment.id;
this.depth = comment.depth;
this.subject = comment.subject;
this.descendants = comment.descendants;
}
var output = [];
// init - pass in data and the root array i.e. output
hydrateComments(comments, output);
// Tada - a hydrated multi-level array
console.log('Iteration output for comments : ', output)