diff --git a/core/src/main/java/apoc/convert/Json.java b/core/src/main/java/apoc/convert/Json.java index f3ce90249a..8736f3716b 100644 --- a/core/src/main/java/apoc/convert/Json.java +++ b/core/src/main/java/apoc/convert/Json.java @@ -234,7 +234,6 @@ public Stream toTree( @Procedure("apoc.paths.toJsonTree") @Description( "apoc.paths.toJsonTree([paths],[lowerCaseRels=true], [config]) creates a stream of nested documents representing the graph as a tree by traversing outgoing relationships") - // todo optinally provide root node public Stream pathsToTree( @Name("paths") List paths, @Name(value = "lowerCaseRels", defaultValue = "true") boolean lowerCaseRels, @@ -254,6 +253,16 @@ public Stream pathsToTree( allPaths.forEach(path -> { // This api will always return relationships in an outgoing fashion ()-[r]->() var pathRelationships = path.relationships(); + // If no relationships exist in the path, then add the node by itself + if (!pathRelationships.iterator().hasNext()) { + Node currentNode = path.startNode(); + Long currentNodeId = currentNode.getId(); + + if (!visitedInOtherPaths.contains(currentNodeId)) { + nodesToKeepInResult.add(currentNodeId); + } + tree.computeIfAbsent(currentNode.getId(), (id) -> toMap(currentNode, nodes)); + } pathRelationships.iterator().forEachRemaining((currentRel) -> { Node currentNode = currentRel.getStartNode(); Long currentNodeId = currentNode.getId(); diff --git a/core/src/test/java/apoc/convert/PathsToJsonTreeTest.java b/core/src/test/java/apoc/convert/PathsToJsonTreeTest.java index 527a6e4f7f..967fd5fbc6 100644 --- a/core/src/test/java/apoc/convert/PathsToJsonTreeTest.java +++ b/core/src/test/java/apoc/convert/PathsToJsonTreeTest.java @@ -56,7 +56,7 @@ public void clear() { } @Test - public void testToTreeSimplePath() throws Exception { + public void testToTreeSimplePath() { /* r:R a:A --------> b:B */ @@ -90,6 +90,68 @@ public void testToTreeSimplePath() throws Exception { } } + @Test + public void testSingleNode() { + // a:A + db.executeTransactionally("CREATE (a: A {nodeName: 'a'})"); + + var query = "MATCH path = (n)\n" + + "WITH COLLECT(path) AS paths\n" + + "CALL apoc.paths.toJsonTree(paths, true, {sortPaths: false}) YIELD value AS tree\n" + + "RETURN tree"; + + try (Transaction tx = db.beginTx()) { + Result result = tx.execute(query); + var rows = result.stream().collect(Collectors.toList()); + var expectedRow = "{" + " \"tree\":{" + + " \"nodeName\":\"a\"," + + " \"_type\":\"A\"," + + " \"_id\":0" + + " }" + + "}"; + assertEquals(rows.size(), 1); + assertEquals(parseJson(expectedRow), rows.get(0)); + } + } + + @Test + public void testSingleDisjointNodes() { + // a:A + db.executeTransactionally("CREATE (a: A {nodeName: 'a'}), (b: B {nodeName: 'b'}), (c: C {nodeName: 'c'})"); + + var query = "MATCH path = (n)\n" + + "WITH COLLECT(path) AS paths\n" + + "CALL apoc.paths.toJsonTree(paths, true, {sortPaths: false}) YIELD value AS tree\n" + + "RETURN tree"; + + try (Transaction tx = db.beginTx()) { + Result result = tx.execute(query); + var rows = result.stream().collect(Collectors.toList()); + var expectedRowA = "{" + " \"tree\":{" + + " \"nodeName\":\"a\"," + + " \"_type\":\"A\"," + + " \"_id\":0" + + " }" + + "}"; + var expectedRowB = "{" + " \"tree\":{" + + " \"nodeName\":\"b\"," + + " \"_type\":\"B\"," + + " \"_id\":1" + + " }" + + "}"; + var expectedRowC = "{" + " \"tree\":{" + + " \"nodeName\":\"c\"," + + " \"_type\":\"C\"," + + " \"_id\":2" + + " }" + + "}"; + assertEquals(rows.size(), 3); + assertEquals(parseJson(expectedRowA), rows.get(0)); + assertEquals(parseJson(expectedRowB), rows.get(1)); + assertEquals(parseJson(expectedRowC), rows.get(2)); + } + } + @Test public void testToTreeSimpleReversePath() { /* r:R