-3

I'm working on a simple CMS for a pet project. I currently have a JSON string that contains a list of page ID's and Parent page ID's for a menu structure.

I want to now convert this string into a nested or hierarchical list (ordered list).

I've tried looking looping through but seem to have ended up with an overly complex range of sub classes. I'm struggling to find a suitable light-weight solution in PHP.

Here's the JSON:

**[{"id":3,"children":[{"id":4,"children":[{"id":5}]}]},{"id":6},{"id":2},{"id":4}]**

Here's the desired output:

<ol>
  <li>3
    <ol>
      <li>4</li>
         <ol>
            <li>5</li>
         </ol>
    </ol>
  </li>
  <li>6</li>
  <li>2</li>
  <li>4</li>
</ol>

Is there anything built in to PHP that can simplify this process? Has anyone had any experience of this before?

I'm a newbie to PHP and SE. Looking forward to hearing from you.

Here's my current progress (it's not working too well)

<ol>
<?php
$json = '[{"id":3,"children":[{"id":4,"children":[{"id":5}]}]},{"id":6},{"id":2},{"id":4}]';
$decoded = json_decode($json);
$pages = $decoded;
foreach($pages as $page){
   $subpages = $decoded->children;
      echo "<li>".$page->id."</li>";
    foreach($subpages as $subpage){
        echo "<li>".$subpage->id."</li>";
   }
}
?>
</ol>
ABOO
  • 145
  • 1
  • 8
  • 1
    You can accomplish this with a couple built-in PHP goodies. For starters, use [json_decode](http://www.php.net/manual/en/function.json-decode.php) to convert your JSON string to a PHP array. After that then you can loop through it with a [foreach loop](http://at2.php.net/manual/en/control-structures.foreach.php), or in your case, a nested `foreach`. – Crackertastic May 19 '14 at 13:38
  • @DanielA.White sure - i've been on this all morning. I'll add some of what i've tried so far. – ABOO May 19 '14 at 13:39
  • @Crackertastic thanks for the pointers - I updated my question with a snippet from my current progress. I was hacking it about so the nested tag code is missing. – ABOO May 19 '14 at 13:56
  • @ABOO You are welcome for the pointers. I posted an answer, but I see that you have already selected one. At any rate, glad you were able to get the help you needed. – Crackertastic May 19 '14 at 14:43

5 Answers5

4

You can use recursion to get deep inside the data. If the current value is an array then recursion again. Consider this example:

$json_string = '[{"id":3,"children":[{"id":4,"children":[{"id":5}]}]},{"id":6},{"id":2},{"id":4}]';
$array = json_decode($json_string, true);

function build_list($array) {
    $list = '<ol>';
    foreach($array as $key => $value) {
        foreach($value as $key => $index) {
            if(is_array($index)) {
                $list .= build_list($index);
            } else {
                $list .= "<li>$index</li>";
            }
        }
    }

    $list .= '</ol>';
    return $list;
}

echo build_list($array);
user1978142
  • 7,946
  • 3
  • 17
  • 20
2

What you're looking for is called recursion, which can be done by a function calling itself.

If you solved once to list all nodes of the list in one function, you can then apply the same function for all child-lists. As then those child-lists will do the same on their children, too.

call_user_func(function ($array, $id = 'id', $list = 'children') {
    $ul = function ($array) use (&$ul, $id, $list) {
        echo '<ul>', !array_map(function ($child) use ($ul, $id, $list) {
            echo '<li>', $child[$id], isset($child[$list]) && $ul($child[$list])
                  , '</li>';
        }, $array), '</ul>';
    };
    $ul($array);
}, json_decode('[{"id":3,"children":[{"id":4,"children":[{"id":5}]}]},{"id":6},
                {"id":2},{"id":4}]', TRUE));

As this example shows, the $ul function is called recursively over the list and all children. There are other solutions, but most often recursion is a simple method here to get the job done once you've wrapped your head around it.

Demo: https://eval.in/153471 ; Output (beautified):

<ul>
  <li>3
    <ul>
      <li>4
        <ul>
          <li>5</li>
        </ul>
      </li>
    </ul>
  </li>
  <li>6</li>
  <li>2</li>
  <li>4</li>
</ul>
hakre
  • 193,403
  • 52
  • 435
  • 836
  • man, this is like rocket science code! i gotta be honest this is the correct answer and should be accepted. +1 – user1978142 May 19 '14 at 15:25
  • no, the accepted answer also has recursion in the code. However it perhaps doesn't explain it well. While my code isn't that easily to read than the accepted example probably. – hakre May 20 '14 at 09:02
2

Using a function that can recursively go through your JSON, you can get the functionality you wish. Consider the following code: (this only accounts for an attribute of id as getting listed, as your desired code shows)

$json = '[{"id":3,"children":[{"id":4,"children":[{"id":5}]}]},{"id":6},{"id":2},{"id":4}]';

function createOLList($group) {
    $output = (is_array($group)) ? "<ol>" : "";
    foreach($group as $attr => $item) {
        if(is_array($item) || is_object($item)) {
            $output .= createOLList($item);
        } else {
            if($attr == "id") {
                $output .= "<li>$item</li>";
            }
        }
    }
    $output .= (is_array($group)) ? "</ol>" : "";
    return $output;
}

print(createOLList(json_decode($json)));

This will produce the following HTML output.

<ol>
    <li>3</li>
    <ol>
        <li>4</li>
        <ol>
            <li>5</li>
        </ol>
    </ol>
    <li>6</li>
    <li>2</li>
    <li>4</li>
</ol>
Crackertastic
  • 4,958
  • 2
  • 30
  • 37
0
<?php

$json_array = array();

array_push($json_array, array(
    'id' => 3,
    'children' => array(
        'id'        =>  4,
        'children'  =>  array(
                'id'    =>  5,
            )
        )
));

array_push($json_array, array('id' => 6));
array_push($json_array, array('id' => 2));
array_push($json_array, array('id' => 4));

//your json object
$json_object = json_encode($json_array);
//echo $json_object;

//here is where you decode your json object
$json_object_decoded = json_decode($json_object,true);

//for debug to see how your decoded json object looks as an array
/*
echo "<pre>";
    print_r($json_object_decoded);
echo "</pre>";
*/

echo "<ol>";
foreach($json_object_decoded as $node){
    if(isset($node['id'])){
        echo "<li>" . $node['id'];
        if(isset($node['children'])){
                echo "<ol>";
                    echo "<li>" . $node['children']['id'] . "</li>";
            if(isset($node['children'])){
                echo "<ol>";
                    echo "<li>" . $node['children']['children']['id'] . "</li>";
                echo "</ol>";
            }
            echo "</ol>";
        }
        echo "</li>";
    }
}
echo "</ol>";


?>
cosneu
  • 26
  • 4
  • this is great - however, it seems to only permit one child level. I need it to go to at least 4. Short of hacking your code and adding more loops i'm unsure how I can use it. – ABOO May 19 '14 at 14:24
  • ok, i will edit to permit two child level, but i think the rest you can handle by yourself :). It can be done in multiple ways, you can even create a recursive function which will echo an
      with an corespondin
    1. when a node is having a children
    – cosneu May 19 '14 at 14:40
0

I have found that i have to fix or simplify almost every of the functions above. So here i came with something simple and working, still recursion.

function build_list($array) {
    $list = '<ul>';
    foreach($array as $key => $value) {
        if (is_array($value)) {
            $list .= "<strong>$key</strong>: " . build_list($value);
        } else {
            $list .= "<li><strong>$key</strong>: $value</li>";
        }
    }
    $list .= '</ul>';
    return $list;
} 
build_list(json_encode($json_string),true);
Valentin Rusk
  • 630
  • 5
  • 13