7

Yes, this question has been answered a lot of times already and, trust me, I searched the internet for it. However, I haven't found a good solution after a fair amount of time.

My problem is the following:

Imagine an array of the following structure:

[
  [ 'helpers', 'ConfigHelper.java' ],
  [ 'helpers', 'GenerateRandomString.java' ],
  [ 'helpers', 'package-info.java' ],
  [ 'helpers', 'ScreenshotHelper.java' ],
  [ 'pages', 'LoginPage.java' ],
  [ 'pages', 'package-info.java' ],
  [ 'pages', 'tests', 'LoginPageTest.java' ],
  [ 'pages', 'tests', 'package-info.java' ],
  [ 'pages', 'util', 'package-info.java' ],
  [ 'pages', 'util', 'PageObject.java' ],
  [ 'pages', 'util', 'PageObjectTest.java' ],
  [ 'pages', 'util', 'PrimaryMethods.java' ],
  [ 'webDriverSetup', 'browserDriverFactories', 'ChromeDriverFactory.java'],
]

You can clearly see the duplicates of values. What I am trying to accomplish is a nested object like this:

{
  helpers: {
    "ConfigHelper.java": "",
    "GenerateRandomString.java": "",
    "package-info.java": ""
  },
  pages: {
    "LoginPage.java": "",
    "package-info.java": "",
    tests: {
      "LoginPageTest.java": "",
      "package-info.java": ""
    },
    util: {
      "package-info.java": "",
      "PageObject.java": "",
      "PageObjectTest.java": "",
      "PrimaryMethods.java": ""
    }
  },
  webDriverSetup: {
    browserDriverFactories: {
      "ChromeDriverFactory.java": ""
    }
  }
}

So each array value is basically another level of object, except the last one, which will just have a string as its value.

A promising approach would be array.reduce() like this:

let arrays = [
  [ 'helpers', 'ConfigHelper.java' ],
  [ 'helpers', 'GenerateRandomString.java' ],
  [ 'helpers', 'package-info.java' ],
  [ 'helpers', 'ScreenshotHelper.java' ],
  [ 'pages', 'LoginPage.java' ],
  [ 'pages', 'package-info.java' ],
  [ 'pages', 'tests', 'LoginPageTest.java' ],
  [ 'pages', 'tests', 'package-info.java' ],
  [ 'pages', 'util', 'package-info.java' ],
  [ 'pages', 'util', 'PageObject.java' ],
  [ 'pages', 'util', 'PageObjectTest.java' ],
  [ 'pages', 'util', 'PrimaryMethods.java' ],
  [ 'webDriverSetup', 'browserDriverFactories', 'ChromeDriverFactory.java'],
];

let treeView = {};

arrays.forEach(array => {
  array.reduce(function(o, key) {
    return o[key] = {};
  }, treeView);
});

console.log(treeView);

However, it will obviously always overwrite the values so at the end I will receive an incomplete object.

My question is:

How can I edit the function so that I receive a complete object?
or
What are alternatives to array.reduce()?

3
  • Maybe this has your answer : dev.to/trusktr/you-dont-need-arrayreduce-557f Commented Nov 25, 2019 at 9:06
  • test o[key] before turning it into an object. Commented Nov 25, 2019 at 9:08
  • 2
    Just add an if that checks if the element/key is already in the object and act accordingly. Like it is done in every "how to group an array of objects" question here on SO. If the first layer works, you can add the second layer (tests, util, ...) Commented Nov 25, 2019 at 9:09

2 Answers 2

11

You could save the last value for later using this value as key for an empty string and the rest for creating a nested object.

let arrays = [['helpers', 'ConfigHelper.java'], ['helpers', 'GenerateRandomString.java'], ['helpers', 'package-info.java'], ['helpers', 'ScreenshotHelper.java'], ['pages', 'LoginPage.java'], ['pages', 'package-info.java'], ['pages', 'tests', 'LoginPageTest.java'], ['pages', 'tests', 'package-info.java'], ['pages', 'util', 'package-info.java'], ['pages', 'util', 'PageObject.java'], ['pages', 'util', 'PageObjectTest.java'], ['pages', 'util', 'PrimaryMethods.java'], ['webDriverSetup', 'browserDriverFactories', 'ChromeDriverFactory.java']],
    treeView = arrays.reduce((tree, [...array]) => {
        var last = array.pop();
        array.reduce((o, k) => o[k] = o[k] || {}, tree)[last] = '';
        return tree;
    }, {});

console.log(treeView);
.as-console-wrapper { max-height: 100% !important; top: 0; }

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

5 Comments

Thank you. Currently testing and trying to understand.
Although it is harder to read and understand (at least imo) than crani's answer (also suggested in the comments) it also takes into account the last value. Thanks to all fo you
the part of the inner reduce checks if a property exits and assigns itself or takes an object as default value. the actual object reference is returned as new accumulator for the next iteration or taken as result for assigning the last property.
@NinaScholz this line array.reduce((o, k) => o[k] = o[k] || {}, tree)[last] = ''; assigns result to tree? Could you write please, simplified version for beginners? Your answer is really great and I really would like to understand it.
final = array.reduce((o, k) => { if (!o[k]) o[k] = {}; return o[k]; }, tree); final[last] = '';
2

Just check if the object key already exits.

let treeView = {};

arrays.forEach(array => {
  array.reduce(function(o, key) {
    if (!treeView[key]) {
      return (o[key] = {});
    } else {
      return (o[key] = treeView[key]);
    }
  }, treeView);
});

console.log(treeView);

Comments

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.