Convert Hierarchical DataTable to Json

2020-06-06 05:42发布

问题:

I have a hierarchial data table as follows which generates menu and its sub menus. main menu has parentId as 0. Submenu has parent Ids referring to parentId.

ResourceId   DisplayName   ParentId     Url
-----------------------------------------------
1           Home           0            Some Url
2           Student        0            Some Url
3           Staff          0            Some Url
4           Library        0            Some Url
6           StudentAtt     1            Some Url
7           TimeTable      1            Some Url
8           Staff Att      2            Some Url
9           Book Issue     3            Some Url
10          Book Return    3            Some Url
11          Fee Payment    4            Some Url
12          Book fine      10           Some Url

need to convert it to Json. Below is the code i tried out. I am trying to check if ParentId of SubMenu equals ResourceId of main Menu. But subMenu is not displayed. (variable table is DataTable)

var rows = table.Rows.Cast<DataRow>().ToList();
            var result = rows.Where(x => x["ParentId"].ToString() == "0").GroupBy(r => new { x = r["ResourceId"] }).Select(
                g => new
                {
                    //MenuLevel = g.Key.x,
                    MenuDetails = g.GroupBy(r => new {a = r["DisplayName"], b = r["Url"]}).Select(
                        detail => new
                        {
                            DisplayName = detail.Key.a,
                            Url = detail.Key.b,
                            SubMenu =
                                detail.Where(y => g.Key.x.ToString()==y["ParentId"].ToString()).
                                    GroupBy(r => new {f = r["DisplayName"]}).Select(
                                    subMenu=>new
                                    {
                                        SubMenuDisplayName=subMenu.Key.f
                                    }
                                    )
                        }
                        )
                });

the result i got is as below.

[{"MenuDetails":[{"DisplayName":"Home","Url":null,"SubMenu":[]}]},{"MenuDetails":[{"DisplayName":"Student","Url":null,"SubMenu":[]}]},{"MenuDetails":[{"DisplayName":"Staff","Url":null,"SubMenu":[]}]},{"MenuDetails":[{"DisplayName":"Library","Url":null,"SubMenu":[]}]}]

But expected result is:

[{"MenuDetails":[{"DisplayName":"Home","Url":null,"SubMenu":[{"SubMenuDisplayName":"StudentAtt"},{"SubMenuDisplayName":"TimeTable"}]}]},{"MenuDetails":[{"DisplayName":"Student","Url":null,"SubMenu":[{"SubMenuDisplayName":"Staff Att"}]}]},{"MenuDetails":[{"DisplayName":"Staff","Url":null,"SubMenu":[{"SubMenuDisplayName":"Book Issue"},{"SubMenuDisplayName":"Book Return"}]}]},{"MenuDetails":[{"DisplayName":"Library","Url":null,"SubMenu":[{"SubMenuDisplayName":"Fee Payment "}]}]}]

I also need to display the sub sub menu (which has parent id pointing to resource id of sub menu) Any one please help

回答1:

The issue your having is in

SubMenu = detail.Where(y => ...

, detail is already filtered to rows.Where(x => x["ParentId"].ToString() == "0") so it does not include the child items.

This would be closer to what you want but only returns the first two levels.

var result = rows.Where(x => x["ParentId"].ToString() == "0").GroupBy(r => new { x = r["ResourceId"] }).Select(
    g => new {
        //MenuLevel = g.Key.x,
        MenuDetails = g.GroupBy(r => new { a = r["DisplayName"], b = r["Url"] }).Select(
            detail => new {
                DisplayName = detail.Key.a,
                Url = detail.Key.b,
                SubMenu =
                    rows.Where(y => g.Key.x.ToString() == y["ParentId"].ToString()).
                        GroupBy(r => new { f = r["DisplayName"] }).Select(
                        subMenu => new {
                            SubMenuDisplayName = subMenu.Key.f
                        }
                        )
            }
            )
    });

To create the full hierarchy you need to create objects for each menu item then join them together. This is easier if you create a type to represent your menu item, for example:

public class MenuDeatils {

    public int ID;
    public string Url;
    public string DisplayName;
    public IEnumerable<MenuDeatils> SubMenu;

}

You can then create an object for each item and group them by their ParentIds:

var groups = (from row in rows
              group row by row["ParentId"] into parentGroup
              select new {
                Key = (int)parentGroup.Key,
                Items = parentGroup.Select(r => new MenuDeatils {
                    ID = (int)r["ResourceId"],
                    DisplayName = (string)r["DisplayName"],
                    Url = (string)r["Url"]
                })
              }).ToList();

Note: The queries are enumerated here (.ToList()) so we create one set of MenuDeatils objects.

Next we can set each MenuDetails object's SubMenu property from the groups we have created.

foreach (var menuItem in groups.SelectMany(g => g.Items)) {
    var submenu = groups.SingleOrDefault(g => g.Key == menuItem.ID);
    if (submenu != null) {
        menuItem.SubMenu = submenu.Items;
    }
};

The top level items can be found with:

var result = groups.Single(g => g.Key == 0).Items

, and now have all the descendant menus attached.