Hierarchical tables can be easily merged using repeating merge blocks with the MailMerge TX Text Control .NET Server for ASP.NET
DocumentServer Namespace
MailMerge Class
The MailMerge class is a .NET component that can be used to effortlessly merge template documents with database content in .NET projects, such as ASP.NET web applications, web services or Windows services.
class. But what if the tables are very dynamic with a variable number of columns?

In this case, tables can be dynamically generated using the TX Text Control API by adding nested tables. This method can also be mixed with the MailMerge process by adding a placeholder in your template that will be replaced with the dynamically generated table.

JSON Data

Consider the following JSON data that should be used to generate the table.

[
{
"Row": 1,
"UniqueID": "900-0315918-000",
"Name": "Tim Typer",
"Address": "1 Text Control Dr.",
"City": "Charlotte",
"Zip": "28209",
"State": "NC",
"Country": "US",
"line_items": [
{
"Name": "TX Text Control",
"Description": "Awesome Document Processor",
"Price": 2998.00,
"Quantity": 1,
"additional_items": [
{
"Name": "TX Barcode .NET for Windows Forms",
"Description": "Barcode Generator for Windows Forms",
"Price": 499.00,
"Quantity": 1
},
{
"Name": "TX Spell .NET for Windows Forms",
"Description": "Spell Checking for Windows Forms",
"Price": 499.00,
"Quantity": 1
}
]
}
]
},
{
"Row": 2,
"UniqueID": "800-0315918-000",
"Name": "Kathrina Keyboard",
"Address": "1 Text Control Dr.",
"City": "Charlotte",
"Zip": "28209",
"State": "NC",
"Country": "US",
"line_items": [
{
"Name": "TX Text Control",
"Description": "Awesome Document Processor",
"Price": 2998.00,
"Quantity": 1,
"additional_items": [
{
"Name": "TX Barcode .NET for Windows Forms",
"Description": "Barcode Generator for Windows Forms",
"Price": 499.00,
"Quantity": 1
}
]
}
]
}
]
view raw test.json hosted with ❤ by GitHub

The data contains three hierarchical levels with two data rows on the first level. The JSON string is loaded and converted into a list of JObject elements returned by the DeserializeObject method of the JsonConvert class (Newtonsoft.Json). The CreateTable method is then called with the created list object.

string json = File.ReadAllText(@"data.json");
var data = JsonConvert.DeserializeObject<List<JObject>>(json);
CreateTable(data);
view raw test.cs hosted with ❤ by GitHub

Recursively Adding Tables

The CreateTable method takes the hierarchical data and creates the tables. It is called recursively based on the current hierarchy level. A new table is added based on the number of columns in the current hierarchy level. For each row of data, a new table row is added and the values are added to the table cell text.

In the case where the data value is an array, a new nested table will be added by making a recursive call to the CreateTable method.

// Create a hierarchical table from a list of JSON objects
private void CreateTable(List<JObject> dataObjects) {
// Get the number of columns of the first object
int columns = dataObjects[0].Values().Count();
// Check if the first object contains an array and reduce the number of columns
columns -= dataObjects[0].Values().Count(value => value.Type == JTokenType.Array);
// Add a table with the number of columns
Table table = AddTableAtInputPosition(1, columns);
int row = 1;
// Loop through all objects
foreach (JObject dataObject in dataObjects) {
// Check if the row is not the first row and add a new row
if (row != 1) {
table.Cells.GetItem(row - 1, 1).Select();
textControl1.Selection.Start += textControl1.Selection.Length;
textControl1.Selection.Length = 0;
textControl1.Selection.Start -= 1;
// Add a new row after the current row
table.Rows.Add(TXTextControl.TableAddPosition.After, 1);
// Set the left text distance to 0 to remove the indent
table.Rows.GetItem().CellFormat.LeftTextDistance = 0;
// Split all cells
table.SplitCells();
}
int col = 1;
// Loop through all properties of the object
foreach (var info in dataObject) {
// Check if the property is an array
if (dataObject[info.Key].Type == JTokenType.Array) {
textControl1.Selection.Start = table.Cells.GetItem(row, 1).Start;
table.Rows.Add(TXTextControl.TableAddPosition.After, 1);
// Set the left text distance to indent the nested table
table.Rows.GetItem().CellFormat.LeftTextDistance = 600;
// Select the new row and merge all cells
table.Select(row + 1, 1, row + 1, table.Columns.Count);
table.MergeCells();
// Create a new table for the array
List<JObject> subObject =
dataObject[info.Key]?.Select(x => x as JObject).ToList();
// recursively call the method to create a new nested table
if (subObject != null) {
CreateTable(subObject);
row++;
}
}
else { // Add the value to the table cell
table.Cells.GetItem(row, col).Text = info.Value?.ToString();
col++;
}
}
row++;
}
}
// Add a table at the current input position and return the table object
private Table AddTableAtInputPosition(int rows, int columns) {
textControl1.Tables.Add(rows, columns);
textControl1.Selection.Start -= 1;
return textControl1.Tables.GetItem();
}
view raw test.cs hosted with ❤ by GitHub

Sample Data Results

When this method is called with the sample data, the following table structure will be created.

Creating tables with TX Text Control