gogo-html-template

How to iterate over []map[string]interface{} in Go to generate html table


Am trying to generate a HTML content using html/template in Go . The data is actually an output of SELECT query from different MySQL Tables.

I need help on the below

  1. Am able to generate the HTML but am unable to split the rows. How to iterate over result := []map[string]interface{}{} (I use interface since the number of columns and it's type are unknown prior to execution) to present data in a table format ?
  2. The Columns and Rows aren't matching

Note: Currently result in the playground link contains 2 maps which should be considered as dynamic. It will change depending on the target table.

Here is the playground link which has sample data matching my use case. https://go.dev/play/p/UTL_j1iRyoG

Below is the output HTML which adds all the values as single row which also doesn't match with Columns.

<html>
    <head>
        <meta charset="UTF-8">
        <title>My page</title>
        <style>
        table, th, td {
          border: 1px solid black;
        }
        th, td {
          padding: 10px;
        }
        </style>
    </head>
    <body>
    <table>
        <tr>
        <th>Name</th><th>Colour</th>
        </tr>
        <tr>
        <td>Red</td><td>Apple</td><td>Banana</td><td>Yellow</td>
        </tr>
    </table>
    </body>
</html>

Solution

  • You can do the following:

    var cols []string
    var rows [][]interface{}
    
    for key, _ := range result[0] {
        cols = append(cols, key)
    }
    
    for _, res := range result {
        vals := make([]interface{}, len(cols))
        for i, col := range cols {
            vals[i] = res[col]
        }
        rows = append(rows, vals)
    }
    
    data := struct {
        Title   string
        Columns []string
        Rows    [][]interface{}
    }{
        Title:   "My page",
        Columns: cols,
        Rows:    rows,
    }
    

    And the HTML will need to be modified accordingly:

    <table>
        <tr>
        {{range .Columns}}<th>{{ . }}</th>{{else}}<div><strong>no rows</strong></div>{{end}}
        </tr>
        {{- range .Rows}}
        <tr>
        {{range .}}<td>{{ . }}</td>{{end}}
        </tr>
        {{- end}}
    </table>
    

    https://go.dev/play/p/MPJOMlfQ488


    Note that in Go maps are unordered, so looping over result[0] to aggregate the column names will produce an unordered []string. This means that the user viewing your HTML page multiple times will see inconsistent output. If that's something you want to avoid, you could either choose to sort the columns using package sort, or you could opt to retain the order of sql.Rows.Columns in some way.