Search code examples
javascriptarraysobjectforeach

How To Get The ForEach Loop To Work On Deep Nested Objects


I am trying to get a forEach loop to write a table row that consists of a variable from both a shallow nested object and a deep nested object.

I have this JSON object:

var chardata = [{
    "moveList": [{
        "name"    : "Directional Tests",
        "synergy" : [{ "partner" : "Character A", "moveEqual" : "Direct Synergy for A" }, { "partner" : "Character B", "moveEqual" : "Direct Synergy for B" }]
    },{
        "name"    : "Motion Tests",
        "synergy" : [{ "partner" : "Character A", "moveEqual" : "Motion Synergy for A" }, { "partner" : "Character B", "moveEqual" : "Motion Synergy for B" }]
    },{
        "name"    : "Button Tests",
        "synergy" : [{ "partner" : "Character A", "moveEqual" : "Button Synergy for A" }, { "partner" : "Character B", "moveEqual" : "Button Synergy for B" }]
    }]
}];

And this forEach function:

function buildSynergyTest(data) {
    var table = document.getElementById('CharacterSynergy');
    var html = '\n';

    for (character of data) {
        character.moveList.forEach(function(move, i) {
            html += ` <TR>\n`;
            html += `  <TD>${move.name}</TD>\n`;
            html += `  <TD>${move.synergy[i].partner}</TD>\n`;
            html += `  <TD>${move.synergy[i].moveEqual}</TD></TR>\n`;
        });
    }
    
    table.innerHTML += html;
}

buildSynergyTest(chardata);

When I run the forEach loop, the first column translates perfectly. But the second and third columns do not. What's supposed to happen is this:

<TABLE border=1>
 <TR>
  <TD>Directional Test</TD>
  <TD>Character A</TD>
  <TD>Direct Synergy for A</TD></TR>
 <TR>
  <TD>Motion Test</TD>
  <TD>Character A</TD>
  <TD>Motion Synergy for A</TD></TR>
 <TR>
  <TD>Button Test</TD>
  <TD>Character A</TD>
  <TD>Button Synergy for A</TD></TR>
 <TR>
  <TD>Directional Test</TD>
  <TD>Character B</TD>
  <TD>Direct Synergy for B</TD></TR>
 <TR>
  <TD>Motion Test</TD>
  <TD>Character B</TD>
  <TD>Motion Synergy for B</TD></TR>
 <TR>
  <TD>Button Test</TD>
  <TD>Character B</TD>
  <TD>Button Synergy for B</TD></TR></TABLE>

Instead, I am getting one of two results:

  1. The second and third columns of data (move.synergy.partner and move.synergy.moveEqual) are listed as undefined. But they are defined, all I have to do is a console.log of chardata[0].moveList[0].synergy[0].partner/moveEqual, and they would show up just fine.

  2. If I were to put the [i] symbol in front of synergy, JS breaks with an undefined error. I know that [i] works in for loops, so I have to assume it should also work in this forEach, but for some reason it doesn't.

The only way I can bring up the desired values is hard coding, by deliberating putting a [0] or [1], but that defeats the purpose of the forEach loop.

How do I rewrite the forEach loop so that it can iterate all columns to be like the expected example?


Solution

  • Here is another way of doing it:

    var chardata = [{
        "moveList": [{
            "name"    : "Directional Tests",
            "synergy" : [{ "partner" : "Character A", "moveEqual" : "Direct Synergy for A" }, { "partner" : "Character B", "moveEqual" : "Direct Synergy for B" }]
        },{
            "name"    : "Motion Tests",
            "synergy" : [{ "partner" : "Character A", "moveEqual" : "Motion Synergy for A" }, { "partner" : "Character B", "moveEqual" : "Motion Synergy for B" }]
        },{
            "name"    : "Button Tests",
            "synergy" : [{ "partner" : "Character A", "moveEqual" : "Button Synergy for A" }, { "partner" : "Character B", "moveEqual" : "Button Synergy for B" }]
        }]
    }];
    
    document.querySelector("table").innerHTML = chardata[0].moveList
     .flatMap(ml=>ml.synergy.map(s=>[ml.name,s.partner,s.moveEqual]))
     .sort(([,a],[,b])=>a.localeCompare(b))
     .map(r=>"<tr><td>"+r.join("</td><td>")+"</td></tr>")
     .join("\n");
    table {border-collapse:collapse}
    td {border:1px solid grey;padding:4px}
    tr:nth-child(even) {background-color:#eee}
    <table></table>

    In the first .flatMap() loop I collect all the nested subarrays into a flat array and re-arrange them slightly: the property name is inserted as a first element of the returned array, followed by the partner and moveEqual properties of the array in the synergy property.

    The .sort() then sorts the array according to the second column ("Character A/B"). As the JavaScript sort function is now stable the original order of Directional, Motion and Button tests is maintained.

    The .map() following the sort then creates some HTML that is evetually .join()ed with a newline character and assigned to the .innerHTML of the first table in the document (feel free to insert an #id selector here instead).