Hierarchical data structures are essential for building navigation menus, organization charts, and category trees. This article explores several C# implementations to transform flat data lists into recursive tree formats, suitable for JSON APIs, object-oriented models, and UI dropdowns.
1. Generating Recursive JSON Strings
In some legacy systems or specific integration scenarios, you may need to manually construct a JSON representation of a tree. This method uses StringBuilder to recursively nest child nodes within the parent JSON object.
public string GenerateTreeJson(List<MenuSource> source, string parentKey)
{
var jsonBuilder = new StringBuilder();
jsonBuilder.Append("[");
var children = source.Where(x => x.ParentId == parentKey).ToList();
if (children.Any())
{
for (int i = 0; i < children.Count; i++)
{
var item = children[i];
// Serialize current item and strip trailing brace to inject children property
var baseJson = JsonConvert.SerializeObject(item, new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});
string nestedJson = baseJson.Substring(0, baseJson.Length - 1)
+ $",\"childNodes\":{GenerateTreeJson(source, item.Id)}"
+ "}";
jsonBuilder.Append(nestedJson);
if (i < children.Count - 1) jsonBuilder.Append(",");
}
}
jsonBuilder.Append("]");
return jsonBuilder.ToString();
}
2. Mapping Flat Data to Nested Object Models
A cleaner approach to modern applications invovles mapping flat database records to a recursive model. This maintains type safety and allows the business logic to manipulate the tree as an object graph.
public void PopulateTree(List<NavigationNode> targetList, List<RawRecord> dataSource, Guid? parentId)
{
var filteredItems = dataSource.Where(node => node.ParentId == parentId).ToList();
foreach (var record in filteredItems)
{
var node = new NavigationNode
{
Id = record.Id,
ParentId = record.ParentId,
Label = record.Name,
Path = record.Url,
OrderIndex = record.SortOrder,
IsActive = !record.IsDeleted
};
targetList.Add(node);
// Recursively find and attach children
PopulateTree(node.Children, dataSource, record.Id);
}
}
3. Formatting Hierarchical Data to UI Select Controls
When displaying hierarchical data in a standard HTML <select> element, visual indentation is required to represent depth. This method flattens the tree but adds visual markers like non-breaking spaces and prefixes.
public void CreateIndentedList(List<NavigationNode> flatResult, List<RawRecord> dataSource, Guid? parentId, int level = 0)
{
var branch = dataSource.Where(x => x.ParentId == parentId)
.OrderBy(x => x.SortOrder)
.ToList();
foreach (var item in branch)
{
string indent = "";
for (int depth = 0; depth < level; depth++)
{
indent += HttpUtility.HtmlDecode(" ") + "└ ";
}
var entry = new NavigationNode
{
Id = item.Id,
ParentId = item.ParentId,
Label = $"{indent}{item.Name}",
Path = item.Url,
OrderIndex = item.SortOrder
};
flatResult.Add(entry);
// Recursive call increasing the depth level
CreateIndentedList(flatResult, dataSource, item.Id, level + 1);
}
}
The Navigation Node Model
Below is the data transfer object (DTO) used in the examples above. It includes standard metadata and a recursive collection to hold child elements.
public class NavigationNode
{
public Guid Id { get; set; }
public Guid? ParentId { get; set; }
public string Identifier { get; set; }
public string Label { get; set; }
public string Path { get; set; }
public int OrderIndex { get; set; }
public bool IsActive { get; set; }
public string Permissions { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
// Recursive collection for child nodes
public List<NavigationNode> Children { get; set; } = new List<NavigationNode>();
}