Search code examples
javascriptjsonreactjsreact-bootstrapoptional-chaining

Iterating Into a Table from a complex JSON


I am struggling with optional chaining in JavaScript to map a JSON object to a Table in React. This is the payload:

{
    "originating request": {
        "id":1,
        "name":"ali",
        "markets": [
            {
                "name": "Winner",
                "selections": [
                    {
                        "name": "Manchester United",
                        "probability": "1.0"
                    },
                    {
                        "name": "Tottenham Hotspur",
                        "probability": "0.0"
                    },
                    {
                        "name": "Arsenal",
                        "probability": "0.0"
                    }
                ]
            },
            {
                "name": "Finish Last",
                "selections": [
                    {
                        "name": "Manchester United",
                        "probability": "0.0"
                    },
                    {
                        "name": "Tottenham Hotspur",
                        "probability": "0.0"
                    },
                    {
                        "name": "Arsenal",
                        "probability": "1.0"
                    }
                ]
            }
        ]
    }
}

Previously, there was only one object in the markets array, which is the Winner market. This was my solution for that scenario, where I would filter for the Winner straightaway and iterate through the table:

return (
<div>
 <Table striped bordered hover size="sm" responsive>
            <thead>
              <tr className="same-col-widths">
                <th>Team Name</th>
                <th>Winner</th>
            </thead>
            <tbody>
              {simResult?.markets?.length
                ? simResult.markets
                    .find(t => t.name === "Winner")
                    .selections.map((selection, index) => (
                      <tr key={index}>
                        <td>{selection.name}</td>
                        <td>{selection.probability}</td>
                      </tr>
                    ))
                : null}
            </tbody>
</Table>
</div>
)

However, now there the Finish Last market has just been added in the markets array. I also plan to add more markets in the future. I would like the table head to look something like this:

            <thead>
              <tr className="same-col-widths">
                <th>Team Name</th>
                <th>Winner</th>
                <th>Finish Last</th>
            </thead>

With the team names in one column, and all the probabilities corresponding to the correct market in the relevant columns. What is the best way to go about doing this?


Solution

  • So first of all you want to reformat the input data using a reduce, to collect all the data for each team (each row in the table) so you can more easily map through those when creating the table. Splitting out this logic from the table-creation also makes you more resilient if that input format changes in the future.

    const reformattedData = data["originating request"].markets.reduce(
        (accumulator, market) =>
          market.selections.map(({ name, probability }, index) => ({
            ...accumulator[index],
            "Team name": name,
            [market.name]: probability,
          })),
        [],
      );
    

    This gives you it in the more table-friendly format of:

    [
        { "Team name": "Manchester United", "Winner": "1.0", "Finish Last": "0.0" },
        { "Team name": "Tottenham Hotspur", "Winner": "0.0", "Finish Last": "0.0" },
        { "Team name": "Arsenal", "Winner": "0.0", "Finish Last": "1.0" },
    ]
    

    And then you can use this new object's keys to map and get the list of table headers, and then map through each object's values to create the rows.

     <table>
          <thead>
            <tr>
              {Object.keys(reformattedData[0]).map((header, index) => (
                <th key={index}>{header}</th>
              ))}
            </tr>
          </thead>
          <tbody>
            {reformattedData.map((teamData, index) => (
              <tr key={index}>
                {Object.values(teamData).map((cellInfo, index) => (
                  <td key={index}>{cellInfo}</td>
                ))}
              </tr>
            ))}
          </tbody>
        </table>
    

    See it working in this Code Sandbox