Search code examples
c#odata

Parent-Child Node Selection


So I have a C# app I'm working on, which for one method retrieves an OData response body. These represent item categories. Any parent-child relationship is defined with a Parent_Category element.

My ultimate goal is to present the user with a tree view of the item categories. So that the hierarchy is apparent. Like this.

Item category tree view

Below is the OData response body that's being sent back to me.

{
"@odata.context": "https://api.businesscentral.dynamics.com/v2.0/13bb3475-ed48-4836-8e0d-6188cc004599/Sandbox2/ODataV4/$metadata#Company('CRONUS%20USA%2C%20Inc.')/ItemCategories",
"value": [
    {
        "@odata.etag": "W/\"JzE5Ozc0MDEzNzM2MzQ2Mjc3MDQ5MzQxOzAwOyc=\"",
        "Code": "BEANS",
        "Description": "BEANS",
        "Parent_Category": "",
        "LSE_Outbound_Code": ""
    },
    {
        "@odata.etag": "W/\"JzE4OzU5MTI1NDIzNTQ0MDU4MDc2OTE7MDA7Jw==\"",
        "Code": "CM",
        "Description": "Coffee Makers",
        "Parent_Category": "",
        "LSE_Outbound_Code": ""
    },
    {
        "@odata.etag": "W/\"JzIwOzE3MzEyMjA5NzY5NzA2NzQ1MjYyMTswMDsn\"",
        "Code": "CM_COMMER",
        "Description": "Commercial Models",
        "Parent_Category": "CM",
        "LSE_Outbound_Code": ""
    },
    {
        "@odata.etag": "W/\"JzE4OzkyNzkxOTcxNzAyNjYyMTI3ODE7MDA7Jw==\"",
        "Code": "CM_CONSUM",
        "Description": "Consumer Models",
        "Parent_Category": "CM",
        "LSE_Outbound_Code": ""
    },
    {
        "@odata.etag": "W/\"JzIwOzEyODQzNzYyMTU3OTI4NTE5Mzk1MTswMDsn\"",
        "Code": "FASHION",
        "Description": "Fashion Jewelry",
        "Parent_Category": "",
        "LSE_Outbound_Code": ""
    },
    {
        "@odata.etag": "W/\"JzIwOzEyMjU5NzE2Njg0MzE3ODg3MTc4MTswMDsn\"",
        "Code": "FURNITURE",
        "Description": "Office Furniture",
        "Parent_Category": "",
        "LSE_Outbound_Code": ""
    },
    {
        "@odata.etag": "W/\"JzIwOzE0NTg1NTU0Njg2NjkzNzc2MjM1MTswMDsn\"",
        "Code": "CHAIR",
        "Description": "Office Chair",
        "Parent_Category": "FURNITURE",
        "LSE_Outbound_Code": ""
    },
    {
        "@odata.etag": "W/\"JzE4OzkyMzYzNjkwNTExMzY3MzAwMDE7MDA7Jw==\"",
        "Code": "DESK",
        "Description": "Office Desk",
        "Parent_Category": "FURNITURE",
        "LSE_Outbound_Code": ""
    },
    {
        "@odata.etag": "W/\"JzIwOzExNzIzNjI3NzUxMjcxMTI2MjY3MTswMDsn\"",
        "Code": "TABLE",
        "Description": "Assorted Tables",
        "Parent_Category": "FURNITURE",
        "LSE_Outbound_Code": ""
    },
    {
        "@odata.etag": "W/\"JzE2Ozc3MjM1ODY4ODY4MDQyNjcxOzAwOyc=\"",
        "Code": "MISC",
        "Description": "Miscellaneous",
        "Parent_Category": "",
        "LSE_Outbound_Code": ""
    },
    {
        "@odata.etag": "W/\"JzE5OzU0NjM3MTMyNTc0NDI5MDk0NDIxOzAwOyc=\"",
        "Code": "SUPPLIERS",
        "Description": "Office Supplies",
        "Parent_Category": "MISC",
        "LSE_Outbound_Code": ""
    },
    {
        "@odata.etag": "W/\"JzIwOzEyNDU0NDkzNDczMDM2NjAxNDk4MTswMDsn\"",
        "Code": "PARTS",
        "Description": "Parts",
        "Parent_Category": "",
        "LSE_Outbound_Code": ""
    }
]

}

Any suggestions about where I can start breaking these out? Before I get into the UI aspects of the result, even a text listing of them would be a start. Such as:

BEANS
CM
--> CM_COMMER
--> CM_CONSUM
...

I figure I need to add these elements into a List and then recursively iterate through that. Just having trouble getting started on the thought process.


Solution

  • This is the easiest i can think of. As linked in the comments there might be better options for creating a tree like structure from a flat list. However this is complete:

    using Newtonsoft.Json;
    
    public class Root
    {
        [JsonProperty("@odata.context")]
        public string OdataContext { get; set; }
        
        [JsonProperty("value")]
        public List<ItemCategory> Categories { get; set; }
    }
    
    public class ItemCategory
    {
        public string Code { get; set; }
        public string Description { get; set; }
        public string Parent_Category { get; set; }
    }
    
    public class TreeNode
    {
        public string Key { get; set; }
        public string Value { get; set; }
        public List<TreeNode> Children { get; set; }
    
        public TreeNode()
        {
            Children = new List<TreeNode>();
        }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            // I stored te json in a file 
            // Your api call would be here
            string filePath = "data.json";
            string json = File.ReadAllText(filePath);
          
    
            // Get the Categories
            var rootjson = JsonConvert.DeserializeObject<Root>(json);
            List<ItemCategory> itemList = rootjson.Categories;
    
            // Build the tree
            var root = new TreeNode();
    
            // I use a dictionary for lookup this could be done without 
            Dictionary<string, TreeNode> tree = new Dictionary<string, TreeNode>();
    
            foreach (var category in itemList)
            {
                // Create the node 
                var treeNode = new TreeNode
                {
                    Key = category.Code,
                    Value = category.Description
                };
    
                // Add the node 
                if (string.IsNullOrEmpty(category.Parent_Category))
                {
                    root.Children.Add(treeNode);
                }
                else
                {
                    // If the parent is found, the current category is added to Children list.
                    if (tree.TryGetValue(category.Parent_Category, out var parent))
                    {
                        parent.Children.Add(treeNode);
                    }
                }
    
                tree.Add(category.Code, treeNode);
            }
    
            PrintTree(root, 0);
        }
    
        static void PrintTree(TreeNode node, int level)
        {         
            // PadLeft does the indentation              
            Console.WriteLine($"{"".PadLeft(level * 2)}{node.Key} - {node.Value}");
    
            foreach (var child in node.Children)
            {
                PrintTree(child, level + 1);
            }
        }
    }
    

    Prints :

      BEANS - BEANS
      CM - Coffee Makers
        CM_COMMER - Commercial Models
        CM_CONSUM - Consumer Models
      FASHION - Fashion Jewelry
      FURNITURE - Office Furniture
        CHAIR - Office Chair
        DESK - Office Desk
        TABLE - Assorted Tables
          Test - Office Supplies //Note i added a 3rd lvl for test
      MISC - Miscellaneous
        SUPPLIERS - Office Supplies
      PARTS - Parts