Search code examples
javascripthtmljsonknockout.jsknockout-3.2

Displaying nested JSON as nested list using knockout.js


I have a nested JSON like this:

[
    {
        "Run 1": {
            "1.2.0": {
                "Server": {
                    "TestSuite 1": [
                        {
                            "version": "1.2.0",
                            "type": "server",
                            "testdef": "TestSuite 1",
                            "testcaseid": "TestCase 1",
                            "status": "pass"
                        },
                        {
                            "version": "1.2.0",
                            "type": "server",
                            "testdef": "TestSuite 1",
                            "testcaseid": "TestCase 2",
                            "status": "fail"
                        }
                    ],
                    "TestSuite 2": [
                        {
                            "version": "1.2.0",
                            "type": "server",
                            "testdef": "TestSuite 2",
                            "testcaseid": "TestCase 1",
                            "status": "pass"
                        }
                    ]
                }
            }
        }
    }
]

I want to display it on the html page as an unordered list like this: Bulleted list

but I am seeing the last item repeated multiple times:

enter image description here

This is html with knockout.js bindings:

<div class="row">
  <div class="tree">
    <ul data-bind="foreach: {data: testResults, as: 'item'}">
      <li data-bind="foreach: {data: Object.keys(item), as: 'key'}"><span data-bind="text: 'Run' + key"></span>
        <ul data-bind="foreach: {data: item[key], as: 'item2'}" class="child">
          <li data-bind="foreach: {data: Object.keys(item2), as: 'key2'}"><span data-bind="text: key2"> </span>
            <ul data-bind="foreach: {data: item2[key2], as: 'item3'}" class="child">
              <li data-bind="foreach: {data: Object.keys(item3), as: 'key3'}"><span data-bind="text: key3"> </span>
                <ul data-bind="foreach: {data: item3[key3], as: 'item4'}" class="child">
                  <li data-bind="foreach: {data: Object.keys(item4), as: 'key4'}"><span data-bind="text: key4"> </span>
                    <ul data-bind="foreach: {data: item4[key4], as: 'item5'}" class="child">
                      <li data-bind="foreach: {data: Object.keys(item5), as: 'key5'}"><span data-bind="text: item5.testcaseid, css : {'bg-success' : item5.status == 'pass', 'bg-danger' : item5.status == 'fail'}"> </span><br></li>
                    </ul>
                  </li>
                </ul>
              </li>
            </ul>
          </li>
        </ul>
      </li>
    </ul>
  </div>
</div>

testResults is a ko.observableArray() in my view model which contains the above JSON.

What would be the correct way to display the leaf elements only once


Solution

  • The problem is you're nesting so many foreach bindings that you're getting lost in how many nested loops there are. Your last look at the very end is not needed. You loop through the keys of the test suite objects when you don't need to. Remove that last foreach binding.

    <ul data-bind="foreach: {data: item4[key4], as: 'item5'}" class="child">
        <li>
            <span data-bind="text: item5.testcaseid,
                             css : {'bg-success' : item5.status == 'pass',
                                    'bg-danger' : item5.status == 'fail'}">
            </span><br>
        </li>
    </ul>
    

    Don't do this. Do you really want to have to look at that view and try to maintain that?

    I have two suggestions, either map the results out to arrays and foreach over those or iterate over the properties alone with the help of custom bindings to make it more manageable. You can use this foreachprop binding handler to do this:

    <ul data-bind="foreach: testResults">
        <li data-bind="foreachprop: $data"><span data-bind="text: key"></span>
            <ul data-bind="foreachprop: value">
                <li><span data-bind="text: key"></span>
                    <ul data-bind="foreachprop: value">
                        <li><span data-bind="text: key"></span>
                            <ul data-bind="foreachprop: value">
                                <li><span data-bind="text: key"></span>
                                    <ul data-bind="foreach: value">
                                        <li><span data-bind="text: testcaseid"></span></li>
                                    </ul>
                                </li>
                            </ul>
                        </li>
                    </ul>
                </li>
            </ul>
        </li>
    </ul>
    

    fiddle