Recursively Search in JSON or Javascript Object - javascript

For example:
[{
id:'our-purpose',
title:'Our Purpose',
slug:'/our-purpose',
backgroundImage:'images/bg-our-purpose.jpg',
showInNav:1
},
{
id:'our-people',
title:'Our People',
slug:'/our-people',
backgroundImage:'images/bg-our-people.jpg',
showInNav:1,
subpages:[
{
id:'attorneys',
title:'Attorneys',
slug:'/our-people/attorneys',
subpages:[
{
id:'attorneys-cdb',
title:'Attorneys - Carla DeLoach Bryant',
slug:'/our-people/attorneys/carla'
},
{
id:'attorneys-jad',
title:'Attorneys - Jordan A. DeLoach',
slug:'/our-people/attorneys/jordan'
},
{
id:'attorneys-shh',
title:'Attorneys - Sarah H. Hayford',
slug:'/our-people/attorneys/sarah'
},
{
id:'attorneys-jsp',
title:'Attorneys - Jason S. Palmisano',
slug:'/our-people/attorneys/jason'
},
{
id:'attorneys-ldw',
title:'Attorneys - Lindsey DeLoach Wagner',
slug:'/our-people/attorneys/carla'
},
]
},
{
id:'legal-support',
title:'Legal Support',
slug:'/our-people/legal-support',
subpages:[
{
id:'legal-support-tb',
title:'Legal Support - Theolyn Brock',
slug:'/our-people/attorneys/theolyn'
},
{
id:'legal-support-cd',
title:'Legal Support - Cheri DeFries',
slug:'/our-people/attorneys/cheri'
},
]
},
//...and so on
You'll notice that you could do json[1].subpages[0].subpages[0] but I don't know how deep it's going to be. This is written by a designer client of mine for an AJAX site he's building for a client. I'm trying to generate a navigation amongst other things and need to be able to:
A. Parse this recursively to build a navigation (<ul><li><a>...)
B. Search for a matching id. Like this (but this isn't recursive)[and ignore the for...in, its just for example's sake)
var matchId(id,json){
for(x in json){
if(json[x].id == id){ var theMatch = json[x]; break; }
}
}

This code builds the nav for you:
function buildNavForNode(node) {
var result = "<li id='" + node.id + "'><a href='" + node.slug + "'>" + node.title + "</a>";
if(node.subpages == undefined) {
return result + "</li>";
} else {
return result + buildNavForNodes(node.subpages) + "</li>";
}
}
function buildNavForNodes(nodes) {
var result = "<ul>";
var i = 0;
var len = nodes.length;
for(; i < len; i++) {
result += buildNavForNode(nodes[i]);
}
return result + "</ul>";
}
Here's how you'd use it:
var testData = [
{
id:'our-purpose',
title:'Our Purpose',
slug:'/our-purpose',
backgroundImage:'images/bg-our-purpose.jpg',
showInNav:1
},
{
id:'our-people',
title:'Our People',
slug:'/our-people',
backgroundImage:'images/bg-our-people.jpg',
showInNav:1,
subpages:[
{
id:'attorneys',
title:'Attorneys',
slug:'/our-people/attorneys',
subpages:[
{
id:'attorneys-cdb',
title:'Attorneys - Carla DeLoach Bryant',
slug:'/our-people/attorneys/carla'
},
{
id:'attorneys-jad',
title:'Attorneys - Jordan A. DeLoach',
slug:'/our-people/attorneys/jordan'
},
{
id:'attorneys-shh',
title:'Attorneys - Sarah H. Hayford',
slug:'/our-people/attorneys/sarah'
},
{
id:'attorneys-jsp',
title:'Attorneys - Jason S. Palmisano',
slug:'/our-people/attorneys/jason'
},
{
id:'attorneys-ldw',
title:'Attorneys - Lindsey DeLoach Wagner',
slug:'/our-people/attorneys/carla'
},
]
},
{
id:'legal-support',
title:'Legal Support',
slug:'/our-people/legal-support',
subpages:[
{
id:'legal-support-tb',
title:'Legal Support - Theolyn Brock',
slug:'/our-people/attorneys/theolyn'
},
{
id:'legal-support-cd',
title:'Legal Support - Cheri DeFries',
slug:'/our-people/attorneys/cheri'
},
]
}
]
}
];
$(function(){
htmlToInsert = buildNavForNodes(testData);
console.log(htmlToInsert);
$('body').html(htmlToInsert);
});
You can do this quite readily with a recursive function, but I think this nicely delineates the separation of duties between figuring out what to do with a collection of pages and processing a single page itself.

Here's a start (in some mix of JavaScript and pseudocode):
function createMenu(data) {
create UL
for each item in data {
create LI for item in UL
if the item has subpages {
append createMenu(item.subpages) to the LI
}
}
return UL
}
function findByID(data, id) {
for each item in data {
if(item.id==id) {
return the item
}
if item has subpages {
if findByID(item.subpages, id) is not null, return the result
}
}
return null;
}

function matchId(id, json){
if (!(json && "object" === typeof json)) { return; }
if (json.id === id) { return json; }
for (var x in json){
if (Object.hasOwnProperty.call(json, x)) {
var result = matchId(id, json[x]);
if (result !== undefined) { return result; }
}
}
}

I would give a try for JSONPath you can find the code here.

I generated the nav with this code since I only wanted the first level:
$('#sidebar').append('<ul></ul>');
for(x in PAGES){
if(PAGES[x].showInNav == 1){
$('#sidebar > ul').append('<li data-id="'+PAGES[x].id+'">'+PAGES[x].title+'</li>');
if(PAGES[x].subpages){
$('#sidebar > ul > li:last').append('<ul></ul>');
for(y in PAGES[x].subpages){
$('#sidebar > ul > li:last > ul').append('<li data-id="'+PAGES[x].subpages[y].id+'">'+PAGES[x].subpages[y].title+'</li>');
}
}
}
}
Then, for the recursive match function I ended up with this code:
var matchKey = function(k,v,j){
k = k || 'id'; //key
v = v || ''; //value
j = j || PAGES; //json
for(x in j){
if(j[x][k] == v){
return j[x];
}
if(j[x].subpages){
var result = matchKey(k,v,j[x].subpages);
if(result !== undefined){
return result;
}
}
}
}

Related

Building an AST in JavaScript

I am attempting to build a parser for BBCode in JavaScript that will allow me to transpile a string with BBCode in it to a string with HTML. I have in my head how it is all supposed to work and I even have two of the parser steps built.
Right now the entire process of the parser can be described as
Get input
Break input into tokens (tokenize)
Add information about the tokens (lex)
Build the AST from the tokens (parse)
Clean up the AST based on grammar rules (clean)
Evaluate AST and transform to HTML (evaluate)
Return the HTML string
I have a general idea of how to do all of this in my head except for step four.
When I reached step four I ran into a problem when building the AST. The problem was how would I go about recursively building this tree. I have in the past recursively built two dimensional arrays but a variable depth tree is way out of my scope of abilities.
In my head I think that the tree should look something like this:
// Hello, [b]World![/b]
{
"text": "Hello, ",
"tag": {
"type": "b",
"text": "World!"
}
}
But when trying to generate this I have an issue with recursively building this down.
A more complex example would be as follows:
// [c=red]Hello Tom, [/c][b][c=green]how are you?[/c][/b]
{
"tag": {
type: "c",
"parameters": "red",
"text": "Hello Tom, "
"tag": {
"type": "b",
"tag": {
"type": "c",
"parameters": "green",
"text": "how are you?"
}
}
}
}
The main issue I run across is keeping my place while building down without accidentally overwriting the entire tree.
Currently the code I am using is:
var bbcode = {};
bbcode._tokens = {
'TO_DEL': '[',
'TC_DEL': ']',
'TE_DEL': '/',
'EQ_DEL': '='
};
bbcode._tags = ['c', 'b'];
bbcode.parse = function(bbcode) {
var tokens = this._tokenize(bbcode);
tokens = this._lex(tokens);
var ast = this._parse(tokens);
console.log(JSON.stringify(ast, null, 4));
//return tokens;
};
bbcode._isToken = function(token) {
for (var k in this._tokens) {
if (this._tokens[k] === token) {
return true;
}
}
return false;
};
bbcode._isTag = function(token) {
return (this._tags.indexOf(token) > -1) ? true : false;
};
bbcode._getType = function(token) {
for (var k in this._tokens) {
if (this._tokens[k] === token) {
return k;
}
}
};
bbcode._next = function(tokens, curr) {
return tokens[curr + 1][0];
};
bbcode._previous = function(tokens, curr) {
return tokens[curr - 1][0];
};
bbcode._tokenize = function(bbcode) {
var tree = [];
var temp = '';
for (var i = 0; i < bbcode.length; i++) {
if (this._isToken(bbcode[i])) {
if (temp.length > 0) {
tree.push(temp);
temp = '';
}
tree.push(bbcode[i]);
} else {
temp += bbcode[i];
}
}
return tree;
};
bbcode._lex = function(tokens) {
var tree = [];
for (var i = 0; i < tokens.length; i++) {
if (this._isToken(tokens[i])) {
tree.push([this._getType(tokens[i]), tokens[i]]);
} else if (this._isTag(tokens[i])) {
tree.push(['BB_TAG', tokens[i]]);
} else {
tree.push(['BB_STRING', tokens[i]]);
}
}
return tree;
};
/*****************************************************************************/
/* I need help with the block below */
/*****************************************************************************/
bbcode._parse = function(tokens) {
var tree = {};
for (var i = 0; i < tokens.length; i++) {
if (tokens[i][0] === 'BB_STRING') {
if (tree['text']) {
tree['text'] += tokens[i][1];
} else {
tree['text'] = tokens[i][1];
}
} else if (tokens[i][0] === 'TO_DEL') {
if (this._next(tokens, i) === 'BB_TAG') {
tree['tag'] = {};
} else {
if (tree['text']) {
tree['text'] += tokens[i][1];
} else {
tree['text'] = tokens[i][1];
}
}
}
}
return tree;
};
/*****************************************************************************/

Loop JSON and stop when condition met. Then count the objects

I have a json file with a list of users and points. I want to somehow Loop through until the _id == userId, then count how many objects there are to get their position.
So far I have this json file which has the points in desc order already
[
{
"_id":"55db8684ce3bf55b4b612a72",
"firstname":"Billy",
"lastname":"Bob",
"points":3109373
},
{
"_id":"55dbdffeaba8ee274d3b9f89",
"firstname":"Steve",
"lastname":"Jones",
"points":34434
},
{
"_id":"55dbdbf756b0fa064dd3e507",
"firstname":"Jim",
"lastname":"Kirk",
"points":1000
},
{
"_id":"55dbdc2756b0fa064dd3e508",
"firstname":"Casey",
"lastname":"Jones",
"points":36
},
{
"_id":"55dbdbd656b0fa064dd3e506",
"firstname":"Reg",
"lastname":"Barclay",
"points":33
},
]
What I need to do is find the position for the user using their ID. So far I have but the position always returns undefined.
$.each(obj, function(index, value) {
var returnObj = (this === "<%= user._id %>");
var position = returnObj.length;
console.log('user position is ' + position);
});
But this always returns undefined 11 times, which is what the position should be.
If I got you right, using for instead of each works which is much faster and not much to code.
try this,
for(var i =0;i < obj.length; i++)
{
if(obj[i]['_id'] == "55dbdc2756b0fa064dd3e508"){
alert('position :' + parseInt(i + 1)); //<--position in an obj
alert('index :' + i); //<----actual index
break;
}
}
var obj=[
{
"_id":"55db8684ce3bf55b4b612a72",
"firstname":"Billy",
"lastname":"Bob",
"points":3109373
},
{
"_id":"55dbdffeaba8ee274d3b9f89",
"firstname":"Steve",
"lastname":"Jones",
"points":34434
},
{
"_id":"55dbdbf756b0fa064dd3e507",
"firstname":"Jim",
"lastname":"Kirk",
"points":1000
},
{
"_id":"55dbdc2756b0fa064dd3e508",
"firstname":"Casey",
"lastname":"Jones",
"points":36
},
{
"_id":"55dbdbd656b0fa064dd3e506",
"firstname":"Reg",
"lastname":"Barclay",
"points":33
},
]
for(var i =0;i < obj.length; i++)
{
if(obj[i]['_id'] == "55dbdc2756b0fa064dd3e508"){
alert('position :' + parseInt(i + 1));
alert('index :' + i);
break;
}
}

javascript recursive object unordered list keep same index for parent

In this DEMO you can see the red <b> tags are showing a different index from the black ones below, this happens why I am trying to use the same index for both <li> and parents <ul>, but something is not working properly... it seems like, only at the very first step, the index counting in skipped and all the remaining index then is "slipped" one step forward, so they are not matching...
var object = {
sometdhing: {
sometfhing: {
somgething: 'text',
someathing: 'text'
},
someathing: {
somefthing: 'text',
sometghing: 'text'
}
},
someathing: {
somethfing: {
somgething: 'text',
somethihng: 'text'
},
somejthing: {
somethhing: 'text',
somfething: 'text'
}
}
}
var indexes = [];
var object2ul = function (data, level) {
var keys = Object.keys(data);
var json = '<ul>'+ '<b style="color:red">'+level+'</b>';
for (var i=0; i<keys.length; ++i) {
var key = keys[i];
indexes.push(i);
json += '<li>' + "<b>" + indexes.join('_') + "</b> - " + key;
if (typeof(data[key]) === 'object') {
json += object2ul(data[key], indexes.join('_'));
} else {
json += '<ul><li>' + data[key] + "</li></ul>";
}
json += "</li>";
indexes.pop();
}
return json + "</ul>";
}
document.body.innerHTML = object2ul(object, 0);
Please help...
Css did help
Code: http://jsfiddle.net/18obnr9L/3/
here i construct elements from object like ul, li, ol...
var data = {
sometdhing: {
sometfhing: {
somgething: 'atext1',
someathing: 'atext2'
},
someathing: {
somefthing: 'atexta1',
sometghing: 'atexta2'
}
},
someathing: {
somethfing: {
somgething: 'btext1',
somethihng: 'btext2'
},
somejthing: {
somethhing: 'btexta1',
somfething: 'btexta2'
}
}
};
list(data);
function list(data) {
var ul = document.createElement('ol'),
li = document.createElement('li')
main = ul.cloneNode();
getProps(main, data);
document.body.appendChild(main);
function getProps(parent, d) {
for (var i in d) {
if (typeof d[i] == "string") {
addLi(parent, d[i], i);
} else if (d[i] instanceof Object) {
var pli = addLi(parent, '', i);
var temp = ul.cloneNode();
getProps( temp, d[i] )
pli.appendChild(temp);
parent.appendChild(pli);
}
}
}
function createUli(val){
var ul = document.createElement('ul');
var l = li.cloneNode();
ul.appendChild(l);
l.innerText = val;
return ul;
}
function addLi(p, val, i) {
var ele = li.cloneNode();
ele.innerText = i;
if (val != '') ele.appendChild(createUli(val));
p.appendChild(ele);
return ele;
}
}

Recursively pass through Json to get all values for a specific key

I have a json object which looks like this:
var testJ = {"ROOT":{
dir : 'app',
files : [
'index.html',
{
dir : 'php',
files: [
'a.php',
{
dir : 'extras',
files : [
'a.js',
'b.js'
]
}
]
}
]
}};
I need to extract all the files and append into an array (index.html,a.php,a.js..etc)
For this I wrote a javascript code as follows:
var arr=[];
function scan(obj,append)
{
var k;
if (obj instanceof Object) {
for (k in obj){
if (obj.hasOwnProperty(k)){
if(k=='files')
{
scan( obj[k],1 );
}
}
}
} else {
body += 'found value : ' + obj + '<br/>';
if(append == 1)
arr.push(obj);
alert("Arr"+ arr);
};
};
scan(testJ,0);
I am not able to figure out where am I going wrong. Could some give me pointers?
var res = [];
function gather(j) {
for (var k in j) {
if (k === 'files') {
addFiles(j[k]);
} else if (typeof j[k] === 'object') {
gather(j[k]);
}
}
}
function addFiles(f) {
for (var i = 0; i < f.length; i++) {
if (typeof f[i] === "string") {
body += 'found value : ' + obj + '<br/>';
res.push(f[i]);
} else {
gather(f[i]);
}
}
}
gather(testJ);
Free tips:
instanceof is some cancerous stuff. Why does instanceof return false for some literals?
Always, always use === for comparison, not ==
I also wouldn't blindly use hasOwnProperty unless you're afraid the thing you're operating on might have a modified prototype, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in just for simplicity.
How about a map reduce approach?:
function mapJ(subject) {
return subject.files.map(function(item) {
if (typeof item === "string") {
return item;
} else {
return parseJ(item);
}
});
}
function reduceJ(subject) {
return subject.reduce(function(prev, cur) {
return prev.concat(cur);
}, []);
}
function parseJ(subject) {
return reduceJ(mapJ(subject));
}
var result = parseJ(testJ));

Need help iterating through a complex Json (no jquery)

I need to create a symfony2 bundle that generates a sidebar from a YAML file
I created this YAML structure
Sidebar:
- Frontpage:
- Dashboard:
_icon: 'icon-home'
_route: 'link'
- Actions:
- My_Likes:
_icon: 'icon-dislike'
_route: 'link'
- My_Dislikes:
_icon: 'icon-home'
_route: 'link'
- Interests:
- Add_Interest:
_icon: 'icon-home'
_route: 'link'
which returns this JSON as a response.
{
"Sidebar": [
{
"Frontpage": [
{
"Dashboard": {
"_icon": "icon-home",
"_route": "link"
}
}
]
},
{
"Actions": [
{
"My_Likes": {
"_icon": "icon-dislike",
"_route": "link"
}
},
{
"My_Dislikes": {
"_icon": "icon-home",
"_route": "link"
}
}
]
},
{
"Interests": [
{
"Add_Interest": {
"_icon": "icon-home",
"_route": "link"
}
}
]
}
]
}
Using ajax, the json is returned on the 'data' variable on the client side
Sidebar.model.request(function(data)
{
for(var a=0; a< data.Sidebar.length; a++ )
{
console.log(data.Sidebar[a]);
}
});
I need to find a way to iterate through the parents and find the corresponding children.
I only need help creating the for loop, so a solution using console.log(data[stuff]); would be enough
EDIT:
here is the adjusted snippet of Daniel Rosano's code
Sidebar.model.request(function(data)
{
//Get Sidebar items
var SidebarItems = data.Sidebar;
//Find Categories in Sidebar Items
for(var a=0; a< SidebarItems.length; a++ )
{
var category = SidebarItems[a];
//Get Category name and append it to sidebar
var category_name = getSubitemName(category);
Sidebar.view.renderCategory(category_name);
//find subitems in categories
for(var b=0; b < category[category_name].length; b++)
{
var button = category[category_name][b];
var button_name = getSubitemName(button);
var button_attributes = button[button_name];
console.log(button_attributes['_icon']);
Sidebar.view.renderButton(button_name);
}
}
function getSubitemName(parent)
{
for(child in parent)
{
return child.toString();
}
}
});
this is the result, thanks Daniel
I know you've already accepted an answer, but I had already written this and then got distracted before posting so I thought I'd share it anyway.
It's a recursive iterator that walks through any arrays or objects it finds in whatever you pass in. It also keeps track of the "path" down to any particular item and the level (mostly for illustrative purposes, but it could be otherwise useful too). A general purpose iterator that would work for any data passed in, pretty much has to be recursive to handle arbitrary depth.
function iterate(item, path, level) {
level = level || 0;
path = path || "root";
if (typeof item === "object") {
if (Array.isArray(item)) {
out("iterating array: " + path, level);
for (var i = 0; i < item.length; i++) {
iterate(item[i], path + "[" + i + "]", level + 1);
}
} else {
out("iterating object: " + path, level);
for (var prop in item) {
// skip any properties on the prototype
if (item.hasOwnProperty(prop)) {
iterate(item[prop], path + "." + prop, level + 1);
}
}
}
} else {
// leaf level property
out(path + " = " + item, level);
}
}
Working demo to see how the path and level work: http://jsfiddle.net/jfriend00/k8aosv59/
Not sure if this is what you need
for (var a = 0; a < t.Sidebar.length; a++) {
var children = t.Sidebar[a];
for (k in children) {
var subchild = children[k];
for (m in subchild) {
var sschild = subchild[m];
for (n in sschild) {
// menu variable has the inner childs (having "_icon" and "_route")
var menu = sschild[n];
console.log(menu._icon+ " "+menu._route);
}
}
}
}
Hope it helps
Dan
You can do it recursively:
function iterate(obj) {
console.log(obj);
for (var key in obj) {
var items = obj[key];
for(var i=0,l=items.length;i<l;i++) {
iterate(items[i]);
}
}
}
iterate(data);
Fiddle

Categories

Resources