1

I'm trying to morph a series of file paths into a nested recursive structure for an Angular Material Tree component. The structure needs to be this:

interface FileTreeNode {
    name: string;
    children?: FileTreeNode[];
}

So a list of file paths such as ['path/to/file/01.txt', 'path/to/file/02.txt', 'another/to/file/03.txt'] will transform into:

[
    {name: 'path', children: [
        {name: 'to', children: [
            {name: 'file', children: [
                {name: '01.txt'}, {name: '02.txt'}
                ]
            }]
        }]
    },
    {name: 'another', children: [
        {name: 'to', children: [
            {name: 'file', children: [
                {name: '03.txt'}
                ]
            }]
        }]
    }
]

I saw a great answer for this problem here, which uses the built-in Array.reduce() method, but I'd like to accomplish this with RxJs operators.

Here is the code I'm trying to replicate:

var paths = ["path/to/file1.doc", "path/to/file2.doc", "foo/bar.doc"],
    result = [];
    
paths.reduce((r, path) => {
    path.split('/').reduce((o, name) => {
        var temp = (o.children = o.children || []).find(q => q.name === name);
        if (!temp) o.children.push(temp = { name });
        return temp;
    }, r);
    return r;
}, { children: result });

console.log(result);

Here's what I have so far:

reduce((acc, curr) => {
    return curr.split('/').reduce((obj, name) => {
        let temp = (obj.children != null ? obj.children : obj.children = []).find(node => node.name === name);
        if (temp == null) {
            obj.children.push(temp = {name});
        }
        return temp;
    }, acc);
}, {children: []} as FileTreeNode),

But this is only returning the last node in the observable (eg. {name: 03.txt}). What I want to be passing along is the final value of acc as the above code does. I also would like to accomplish this using just the RxJs operators, instead of having to rely on the internal Js Array.reduce() function if possible.

Thanks!

1
  • It's an interesting exercise, but unless you have an asynchronous stream of file paths segments, you should stick with Array.prototype.reduce Commented Jan 18, 2021 at 23:02

1 Answer 1

1

Use reduce exactly the same way, The difference is that you need to convert the array to a stream. You can use from operator

  paths = ["path/to/file1.doc", "path/to/file2.doc", "foo/bar.doc"];
  paths$ = from(this.paths).pipe(
    reduce(
      (r, path) => {
        path.split("/").reduce((o, name) => {
          var temp = (o.children = o.children || []).find(q => q.name === name);
          if (!temp) o.children.push((temp = { name }));
          return temp;
        }, r);
        return r;
      },
      { children: [] }
    )
  );

See this demo

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you. The issue was that my sonar lint goes haywire because I'm not returning the value of the reduce, but rather the accumulator object. That's what kept me from doing the return r; statement as you have.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.