create nested list from JSON

  • (2 Pages)
  • +
  • 1
  • 2

15 Replies - 33538 Views - Last Post: 28 August 2014 - 01:02 PM

#1 Dormilich   User is offline

  • 痛覚残留
  • member icon

Reputation: 4303
  • View blog
  • Posts: 13,677
  • Joined: 08-June 10

create nested list from JSON

Post icon  Posted 09 November 2011 - 06:39 AM

Hi,

I’m trying to get a JSON string (see below) to parse into a nested list (with no limit to the nesting level) by means of jQuery (since I use $.getJSON to get the string). currently I have a working but very un-jQuery method via a recursive function. from what I found in the web, mostly fixed dephth lists were used so these methods didn’t apply.

anyone an idea how to do that more jQuery-like?

// JSON
[
    {
        "title": "Home",
        "href" : "example.com/home",
        "children": [
            {
                "title": "about me",
                "href" : "example.com/me",
                "children": [
                    // I think you get the point …
                ]
            }
        ]
    },
    {
        "title": "sitemap",
        "href" : "example.com/sitemap",
    }
]

// current recursive function
function createItem(obj)
{
    var html = '<li><a href="' + obj.href + '">' + obj.title + "</a>";
    if (obj.children && obj.children.length) {
        html += '<ul>';
        for (var i = 0, l = obj.children.length; i < l; i++) {
            html += createItem(obj.children[i]);
        }
        html += '</ul>';
    }
    return html + '</li>';
}

This post has been edited by Dormilich: 09 November 2011 - 06:41 AM


Is This A Good Question/Topic? 1
  • +

Replies To: create nested list from JSON

#2 JackOfAllTrades   User is offline

  • Saucy!
  • member icon

Reputation: 6260
  • View blog
  • Posts: 24,030
  • Joined: 23-August 08

Re: create nested list from JSON

Posted 09 November 2011 - 09:46 AM

Damn, if Dormilich can't do it...I sure as hell can't! Will be monitoring for the solution though...might come in handy!
Was This Post Helpful? 0
  • +
  • -

#3 Dormilich   User is offline

  • 痛覚残留
  • member icon

Reputation: 4303
  • View blog
  • Posts: 13,677
  • Joined: 08-June 10

Re: create nested list from JSON

Posted 09 November 2011 - 10:29 AM

if I could use RecursiveArrayIterator and foreach(), that wouldn’t be a problem.

maybe I should ask for a general solution with callbacks in Functional Programming or Computer Science …

This post has been edited by Dormilich: 09 November 2011 - 10:32 AM

Was This Post Helpful? 0
  • +
  • -

#4 joezim007   User is offline

  • D.I.C Head
  • member icon

Reputation: 11
  • View blog
  • Posts: 110
  • Joined: 13-September 08

Re: create nested list from JSON

Posted 09 November 2011 - 11:54 AM

Due to your initial JSON object being an array instead of starting with one object, I'm assuming you put a for loop around the outside calls to createItem. I've made the createItem function better by allowing you to send either an array or an object. If it's the object, it'll make the li and then send any children it may have to a recursive call, but if it's an array, it'll create a ul and then loop through it and send the objects to more recursive calls of createItem.

Here's the code:
function createItem(obj)
{
	var $obj = null;
	
	if (obj.title) {
		// it's an object, so create the <li> and <a>
		$obj = $('<a>').attr('href', obj.href).text(obj.title);
		$obj = $('<li>').append($obj);
		
		// if there are any children, append them recursively
		if (obj.children) {
			$obj.append(createItem(obj.children));
		}
	}
	else if (obj.length) {
		// it's an array with some elements, so create the <ul>
		$obj = $('<ul>');
		
		for (var i = 0, l = obj.length; i < l; i++) {
			$obj.append( createItem( obj[i] ) );
		}
	}
	
	// if it was an empty array or an object that doesn't have a title
	// property, then it'll just return null;
	return $obj;
}

$('body').append(createItem(JSON_OBJECT));



I'm a bit surprised that you couldn't at least figure out the jQuery part because it's really not all that different from your code. The biggest changes in my code were to handle arrays and objects being sent to it.
Was This Post Helpful? 2
  • +
  • -

#5 Curtis Rutland   User is offline

  • (╯°□°)╯︵ (~ .o.)~
  • member icon


Reputation: 5106
  • View blog
  • Posts: 9,283
  • Joined: 08-June 10

Re: create nested list from JSON

Posted 09 November 2011 - 01:34 PM

Well, if we're doing this in a jQuery-ish way, this could work:

var data = [
	{
		"title": "Home",
		"href" : "example.com/home",
		"children": [
			{
				"title": "about me",
				"href" : "example.com/me",
				"children": [
					{
						"title": "another child",
						"href" : "example.com/me" 
					}
				]
			}
		]
	},
	{
		"title": "sitemap",
		"href" : "example.com/sitemap",
	}
];

$(document).on("ready", function(){
    var ul = $("<ul></ul>");
	$.each(data, function(key, value) {
		var func = (function (item, list){
			if(item){
				if(item.href && item.title){
					$(list).append($("<li></li>", { 
						"html" : $("<a></a>", {
							"href" : item.href, 
							"html" : item.title 
						})
					}));
				}
				if(item.children && item.children.length){
					var sublist = $("<ul></ul>");
					for(index in item.children)
						func(item.children[index], sublist);
					$(list).append($(sublist));
				}
			}
		}); 
		func(value, $(ul));
	});
	$(ul).appendTo("#target");
});


I couldn't make the getJSON stuff work properly, so I just used a local variable. This is all anonymous functions, with a recursive call.

Edit: much more "jQuery-ish" mechanism. Also note that I'm using jQuery 1.7.
Was This Post Helpful? 1
  • +
  • -

#6 Dormilich   User is offline

  • 痛覚残留
  • member icon

Reputation: 4303
  • View blog
  • Posts: 13,677
  • Joined: 08-June 10

Re: create nested list from JSON

Posted 09 November 2011 - 01:36 PM

View Postjoezim007, on 09 November 2011 - 07:54 PM, said:

I'm a bit surprised that you couldn't at least figure out the jQuery part because it's really not all that different from your code.

a) not enough time to think about it
b) I’m not a jQuery expert, because I hardly use it

anyways, thanks for the suggestions, I will have a look at it.

This post has been edited by Dormilich: 09 November 2011 - 01:37 PM

Was This Post Helpful? 0
  • +
  • -

#7 Curtis Rutland   User is offline

  • (╯°□°)╯︵ (~ .o.)~
  • member icon


Reputation: 5106
  • View blog
  • Posts: 9,283
  • Joined: 08-June 10

Re: create nested list from JSON

Posted 09 November 2011 - 01:47 PM

Hey Dorm, I updated my code a bit to use a bit more jQuery features and not as much string concatenation. Let me know if it helps.
Was This Post Helpful? 0
  • +
  • -

#8 joezim007   User is offline

  • D.I.C Head
  • member icon

Reputation: 11
  • View blog
  • Posts: 110
  • Joined: 13-September 08

Re: create nested list from JSON

Posted 09 November 2011 - 05:15 PM

View PostCurtis Rutland, on 09 November 2011 - 03:34 PM, said:

$(document).on("ready", function(){
    var ul = $("<ul></ul>");
	$.each(data, function(key, value) {
		var func = (function (item, list){
			if(item){
				if(item.href && item.title){
					$(list).append($("<li></li>", { 
						"html" : $("<a></a>", {
							"href" : item.href, 
							"html" : item.title 
						})
					}));
				}
				if(item.children && item.children.length){
					var sublist = $("<ul></ul>");
					for(index in item.children)
						func(item.children[index], sublist);
					$(list).append($(sublist));
				}
			}
		}); 
		func(value, $(ul));
	});
	$(ul).appendTo("#target");
});


Why do you keep using $(ul)? ul is already a jQuery object, so you can use it as is. Same thing with sublist. Generally, it is best practice to prefix a variable holding a jQuery object with a $ symbol, so ul and sublist should be $ul and $sublist. Finally, I'm having issues with your $.each loop:
1) If you would use $.each for the initial loop, why wouldn't you use it for the children lists? I guess it's a bit excessive when you're only executing 1 line of code, but you should be consistent.
2) By declaring func within the callback for $.each, that means that func gets remade for each iteration that $.each makes, meaning it's slower, especially on large lists.

Sorry if I sound like a super picky Grinch, I'm just trying to make people be better programmers. Feel free to pick apart my code :)
Was This Post Helpful? 1
  • +
  • -

#9 Curtis Rutland   User is offline

  • (╯°□°)╯︵ (~ .o.)~
  • member icon


Reputation: 5106
  • View blog
  • Posts: 9,283
  • Joined: 08-June 10

Re: create nested list from JSON

Posted 09 November 2011 - 05:52 PM

I tried $.each for the inner loop, but it seemed to hit a function object and stack overflow. I didn't spend the time to debug into it. It might be worth it.

As to the part about already being a jQuery object, I'm sure you're right. I'm not a JS programmer by trade (I mostly do C#), so I'm actually fairly new to jQuery.

As to creating the funciton object, I was actually trying to be clever with anonymous recursion, something that's easier in JS than it is in C#. It would be trivial to extract that method into a named method, and would probably be a better idea.

Edit: Here's updated code with your suggestions. When I used $.each for the inner loop this time, I didn't hit the function object, so no overflow.

$(document).on("ready", function () {
    var $ul = $("<ul/>");
    $.each(data, function (key, value) {
        getList(value, $ul);
    });
    $ul.appendTo("#target");
});

function getList(item, $list) {
    if (item) {
        if (item.href && item.title) {
            $list.append($("<li/>", {
                "html": $("<a/>", {
                    "href": item.href,
                    "html": item.title
                })
            }));
        }
        if (item.children && item.children.length) {
            var $sublist = $("<ul/>");
            $.each(item.children, function (key, value) {
                getList(value, $sublist);
            });
            $list.append($sublist);
        }
    }
}



So, what do you think? Your's is probably more readable.
Was This Post Helpful? 1
  • +
  • -

#10 Curtis Rutland   User is offline

  • (╯°□°)╯︵ (~ .o.)~
  • member icon


Reputation: 5106
  • View blog
  • Posts: 9,283
  • Joined: 08-June 10

Re: create nested list from JSON

Posted 09 November 2011 - 06:28 PM

Also, to address this:

Quote

Sorry if I sound like a super picky Grinch, I'm just trying to make people be better programmers. Feel free to pick apart my code


Don't feel like you have to apologize. I'm a firm believer in the idea that "you are not your code." If you're not willing to hear criticisms, you shouldn't post your code online. I'm always ready to hear ways to improve.
Was This Post Helpful? 0
  • +
  • -

#11 joezim007   User is offline

  • D.I.C Head
  • member icon

Reputation: 11
  • View blog
  • Posts: 110
  • Joined: 13-September 08

Re: create nested list from JSON

Posted 09 November 2011 - 08:04 PM

View PostCurtis Rutland, on 09 November 2011 - 07:52 PM, said:

So, what do you think? Your's is probably more readable.


I think either solution works well. The one thing that I really like about mine though is that I don't have this part:
var $ul = $("<ul/>");
$.each(data, function (key, value) {
    getList(value, $ul);
});
$ul.appendTo("#target");



Instead I just have this:
$('body').append(createItem(JSON_OBJECT));



This decouples things a bit. You don't have know that there is a loop and you don't have to create a UL outside of the function. The function does all that for you without actually adding any more responsibility to the function. That's the beauty of it: I didn't give the function any more responsibilities, but still made it responsible for more... if that makes any sense.

This post has been edited by joezim007: 09 November 2011 - 08:07 PM

Was This Post Helpful? 0
  • +
  • -

#12 Dormilich   User is offline

  • 痛覚残留
  • member icon

Reputation: 4303
  • View blog
  • Posts: 13,677
  • Joined: 08-June 10

Re: create nested list from JSON

Posted 10 November 2011 - 12:02 AM

View Postjoezim007, on 10 November 2011 - 04:04 AM, said:

I think either solution works well. The one thing that I really like about mine though is that I don't have this part:
var $ul = $("<ul/>");
$.each(data, function (key, value) {
    getList(value, $ul);
});
$ul.appendTo("#target");



Instead I just have this:
$('body').append(createItem(JSON_OBJECT));


actually, I need to be the top level <ul> a bit different …

and now for the really interesting question, can I do the reverse (list => JSON) with jQuery as well?
Was This Post Helpful? 0
  • +
  • -

#13 joezim007   User is offline

  • D.I.C Head
  • member icon

Reputation: 11
  • View blog
  • Posts: 110
  • Joined: 13-September 08

Re: create nested list from JSON

Posted 10 November 2011 - 08:21 AM

View PostDormilich, on 10 November 2011 - 02:02 AM, said:

and now for the really interesting question, can I do the reverse (list => JSON) with jQuery as well?


Sounds like you're working with Dynamic lists huh? I'll see if I can find some time later today to work up a solution for you. Shouldn't be too difficult, assuming I can keep recursion working correctly in my head. My brain sometimes has a hard time thinking in a recursive manner.
Was This Post Helpful? 0
  • +
  • -

#14 Dormilich   User is offline

  • 痛覚残留
  • member icon

Reputation: 4303
  • View blog
  • Posts: 13,677
  • Joined: 08-June 10

Re: create nested list from JSON

Posted 10 November 2011 - 09:11 AM

dynamic, sortable* lists with versioning. the list-to-json conversion is needed for the latter. right now list creation/modification is done, what’s left is the version control.


* - I’m so glad that this is nearly a no-brainer with jQuery-UI

This post has been edited by Dormilich: 10 November 2011 - 09:12 AM

Was This Post Helpful? 0
  • +
  • -

#15 Dormilich   User is offline

  • 痛覚残留
  • member icon

Reputation: 4303
  • View blog
  • Posts: 13,677
  • Joined: 08-June 10

Re: create nested list from JSON

Posted 11 November 2011 - 05:17 AM

fyi, the reverse function as it is now
function createObj($li, obj)
{
    var $a = $li.children("a");
    var self = {
        title: $a.text()
    };
    var id = $a.attr("name");
    if (id) {
        self.id = id;
    }
    var $c = $li.children("ul");
    if ($c.length) {
        self.children = [];
        $c.children("li").each(function(index, item) {
            self.children.push(createObj($(item)));
        });
    }
    return self;
}

alternate version:
function createObj(elem)
{
    var $el = $(elem);
    // <ul>
    var $li = $el.children("li");
    if ($li.length) {
        var children = [];
        $li.each(function(index, item) {
            children.push(createObj(item));
        });
        return children;
    }
    // <li>
    var $a = $el.children("a");
    if ($a.length) {
        var self = {
            title: $a.text()
        };
        if ($a.attr("name")) {
            self.id = $a.attr("name");
        }
        var $ul = $el.children("ul");
        if ($ul.length) {
            self.children = createObj($ul);
        }
        return self;
    }
}

This post has been edited by Dormilich: 11 November 2011 - 05:52 AM

Was This Post Helpful? 0
  • +
  • -

  • (2 Pages)
  • +
  • 1
  • 2