简体   繁体   中英

How to convert flat list to nested tree in java with a nested set model?

I'm using the nested set model to store category hierarchies in my postgres database. Im able to query the tree for a given category ID and see all of its children and the depths they sit at. The response for that query looks like this:

[
    {
        "id": "07e0b6c2-49cd-440b-a900-0f3d7ab88022",
        "categoryId": "80d15a99-9e42-4b72-b44b-0b222ca5173e",
        "name": "Root",
        "lft": 1,
        "rgt": 18,
        "depth": 0
    },
    {
        "id": "ae53be00-c312-4cd5-a6b2-6baeaf760577",
        "categoryId": "9b8bca09-2447-494c-be0d-0b3af7d30671",
        "name": "Cat A",
        "lft": 2,
        "rgt": 9,
        "depth": 1
    },
    {
        "id": "0a5d4b90-29b9-4c50-a436-d129dc6983ea",
        "categoryId": "d06a143b-523e-4136-8a17-1049abbf76f4",
        "name": "Cat B",
        "lft": 3,
        "rgt": 4,
        "depth": 2
    },
    {
        "id": "11421455-abc0-464c-8bd0-e2d79302c270",
        "categoryId": "5af63d5b-f480-4620-8393-f4b93f7972e0",
        "name": "Cat D",
        "lft": 5,
        "rgt": 6,
        "depth": 2
    },
    {
        "id": "4463dbce-a2bf-42fe-a864-59309ba54d22",
        "categoryId": "21191930-a5b9-4868-883f-3798f29d70a3",
        "name": "Cat E",
        "lft": 7,
        "rgt": 8,
        "depth": 2
    },
    {
        "id": "0f40e7a0-e6eb-44a4-a9bd-b61512daa236",
        "categoryId": "34b127e8-7a8f-40b3-9b7e-63c8d507cc7b",
        "name": "Cat F",
        "lft": 10,
        "rgt": 11,
        "depth": 1
    },
    {
        "id": "87e9991e-085c-47a5-8357-79c0e467b8ec",
        "categoryId": "dfbbaac7-dda3-4f34-a787-183803f8e6fa",
        "name": "Cat G",
        "lft": 12,
        "rgt": 17,
        "depth": 1
    },
    {
        "id": "8a95b0ab-cf74-4083-9d17-40e70468350a",
        "categoryId": "f7f04485-d089-4a5d-98cd-20b0abeba8fc",
        "name": "Cat H",
        "lft": 13,
        "rgt": 14,
        "depth": 2
    },
    {
        "id": "dccee476-af73-4eb6-a595-f862984d4af6",
        "categoryId": "0eb165ec-0347-4336-8fc2-35c124bf26f2",
        "name": "Cat I",
        "lft": 15,
        "rgt": 16,
        "depth": 2
    }
]

You'll notice the tree is already flattened. I'm trying to put the above structure into a nested tree structure that I can return as JSON to my UI. Ideally something that looks like this (omitted data for brevity):

[
    {
        "name": "Cat A",
        "children": [
            {
                "name": "Cat B",
                "children": [
                    {
                        "name": "Cat C"
                    },
                    {
                        "name": "Cat D"
                    }
                ]
            },
            {
                "name": "Cat E",
                "children": [
                    {
                        "name": "Cat F"
                    }
                ]
            }
        ]
    }
]

I've tried a bunch of options with the most recent being streaming the original list into a map and then collecting its values based on the depth each node sits at. This doesn't get me all the way what I'm looking for as it groups nodes that are at the same level but not necessarily part of the same tree.

List<List<CategoryTree>> listOfLists = new ArrayList<>(
                categoryTreeList.stream()
                        .collect(Collectors.groupingBy(CategoryTree::getDepth))
                        .values());

Seems like I should be trying to key off of the lft and rgt values as well to determine if a node in a given list has any children which would be indicated by a spread greater than a value of 1.

How can I solve this?

I would suggest that you create a class that holds your structure data rather than trying to use a raw map.

class Node {
    private final String id;
    private final String category;
    private Node left = null;
    private Node right = null;

    public Node(String id, String category) {
        this.id = id;
        this.category = category;
    }

    // left and right setters
}

You will need to build the structure in 2 passes.

The first will be creating the nodes and adding them to a list so that their indices can be used.

List<Node> nodes = new ArrayList<>();
for (row: queryResult) {
    nodes.add(new Node(row.id(), row.category()));
}

In the second pass you build the structure:

int i = 0;
for (row: queryResult) {
    Node node = nodes.get(i++);
    if (row.lft())
        node.setLeft(nodes.get(row.lft()));
    if (row.rgt())
        node.setRight(nodes.get(row.rgt()));
}

There are ways of doing it in 1 pass and then resolving forward references but I really doubt it'd be worth it.

If your root node is not guaranteed to be the first in the list then you would need to look through the list finding a node that is not a child of any other node.

There's really no need to store or use the depth unless you particularly need it for something else. And even then it's best to derive it (by maintaining a parent field and walking up the hierarchy) unless the structure is guaranteed to be static.

Converting to json is best done with a library such a gson and then a custom converter for your class that uses recursion to convert the whole structure.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM