0

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.

gregarican
  • 105
  • 1
  • 11
  • It seems like you have all data you need. I would suggest making a list of objects from it and the use groupby to group based in the Parent_Category – jeb Jun 28 '23 at 18:38
  • The top-level nodes don't have a Parent_Category assigned. So I don't see how the GroupBy against the list would work. If the OData was presented as a nested response then it would be more straightforward to parse. In this case it's pretty much a flat response set. I know I'll need to employ recursion in order to ensure I've reached all of the levels. Since there could be more than my basic example shows. – gregarican Jun 28 '23 at 18:46
  • 1
    Maybe this helps ? https://stackoverflow.com/questions/19648166/nice-universal-way-to-convert-list-of-items-to-tree – jeb Jun 28 '23 at 18:59
  • Your parentid would be Parent_Category. You have to handle as an extra the ID when parent_category is empty. Then you could use the Code property – jeb Jun 28 '23 at 19:00

1 Answers1

1

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
jeb
  • 848
  • 5
  • 16
  • This is perfect! Thanks so much. All of the recursion stuff I hadn't thought about or had to use since I started learning C#, Ruby, and Python many years ago. Appreciate the hand. – gregarican Jun 29 '23 at 11:17
  • Here was a great blog article that offers even full functionality. Since I will need to inspect any specific node's ancestors and whatnot --> https://www.siepman.nl/blog/a-generic-tree-of-nodes-the-easy-way. – gregarican Jun 29 '23 at 14:51