简体   繁体   中英

Iterating through JSON using an object

I'm creating a template picker/roulette Slack bot. The goal is to message the bot and pass in arguments like the command line, and have it iterate through a JSON object and randomly select a template that matches the criteria. The user should be able to pass in any amount of arguments too.

Example : I message the bot with this Roulette -s -i the arguments are -s which stands for sidebar and -i which stands for index. So the bot needs to find a template that has both a sidebar and an index.

This is a sample of my JSON object:

{
  name: "foo",
  index: {
    hasIndex: true
  },
  sidebar: {
    hasSidebar: true
  }
}

This is my code:

controller.hears(["roulette", "^pattern$"],["direct_message", "direct_mention", "mention"], function(bot,message) {
  const data = require('./data.js');
  const nodeList = []
  const args = message.text.match(/-\w/g);
  const obj = {
    '-s': '.sidebar.hasSidebar',
    '-i': '.index.hasIndex'
  }

  args.forEach(function(arg) {
    var path = obj[arg];
    // console.log(path) when passing arguments -s or -i
    // the path variable correctly gives me .sidebar.hasSidebar, etc    

    data.forEach(function(node) {
      // console.log(node) Gives me my data set
      // console.log(node.path) This is the issue, it returns undefined.
      if (node.path === true) {
        nodeList.push(node.name)
      }
    });
  });
});

My question is why can I not use node.path if node is correctly giving me the data set? Shouldn't I be able to add path and complete the path so the forEach loops through? Instead I am getting undefined and I don't understand why.

EDIT

I've updated my code:

  var nodeList = data.filter(function(node) {
    return args.every(function(arg) {
      return obj[arg](node);
    });
  });

  const obj = {
    '-s': function (node) { return node.sidebar.hasSidebar; },
    '-i': function (node) { return node.index.hasIndex; },
  };


  data.forEach(function(node) {
    args.forEach(function(arg) {
      var pathTester = obj[arg];

      if (pathTester(node)) {
        nodeList.push(node.name)
      }
    });
  });

1) Did I swap the loops correctly?

2) I don't quite understand how nodeList is supposed to work.

When you write:

if (node.path === true)

...you are looking for the property with name path . This has nothing to do with the variable path which you declared earlier on.

Looking up a dynamic property-chain based on a dot-separated string (like '.sidebar.hasSidebar' ) is not possible with this syntax. You would have to call eval() to achieve that, like this:

if (eval('node' + path) === true)

This works, but because eval has a bad reputation, I would suggest to define your obj with values that are functions instead of strings, like this:

const obj = {
    '-s': function (node) { return node.sidebar.hasSidebar; },
    '-i': function (node) { return node.index.hasIndex; },
};

... and then retrieve and call the function:

args.forEach(function(arg) {
    var pathTester = obj[arg];

    data.forEach(function(node) {
        if (pathTester(node)) {
            nodeList.push(node.name);
        }
    });
});

Correction to Algorithm

Now, the above answers the question, but as you mentioned the following:

Example: I message the bot with this Roulette -s -i the arguments are -s which stands for sidebar and -i which stands for index. So the bot needs to find a template that has both a sidebar and an index.

... I need to point out that your current code will push nodes to the node list even if only one condition is true, and the same nodes may be pushed to it several times.

If you want nodes to only be added when all conditions are true, then swap your loops and make sure you only add nodes when the (then) inner loop returns true for all args.

At the same time I'll suggest the use of every and filter functions:

var nodeList = data.filter(function(node) {
    return args.every(function(arg) {
        return obj[arg](node);
    });
});

Note that this replaces the forEach loops you have. The complete code would look like this:

controller.hears(["roulette", "^pattern$"],["direct_message", "direct_mention", "mention"], function(bot,message) {
    const data = require('./data.js');
    const args = message.text.match(/-\w/g);
    const obj = {
        '-s': function (node) { return node.sidebar.hasSidebar; },
        '-i': function (node) { return node.index.hasIndex; },
    };
    var nodeList = data.filter(function(node) {
        return args.every(function(arg) {
            return obj[arg](node);
        });
    });
});

More about filter and every

The above is taking the power of filter and every . Without these methods, the code could look like this:

var nodeList = [];
data.forEach(function(node) {
    // Check if this node should be added
    var addNode = true; // start positive...
    args.forEach(function(arg) {
        var pathTester = obj[arg];
        if (pathTester(node) !== true) {
            // if not all paths are true, don't add the node 
            addNode = false;
        }
    });
    if (addNode) {
        nodeList.push(node);
    }
});

More about comparing with true

When writing code like this:

if (property === true)

... and you know for sure that property is always a boolean (if it is defined), then you can just write this:

if (property)

This is because an if statement needs something that evaluates to a boolean. But if property already is a boolean, there is no need to make a comparison with true . There are three possibilities for property . Either it is:

  • undefined
  • false
  • true

When one of these three values is the if expression, only the last one true will make the if condition true . undefined is considered a falsy value.

However, if property could also be a non-boolean value, like a number, string or object, and you only want to respond to the exact value of true (not any other value), then you do need to test like === true . Without it, also non-zero numerical values, non-empty strings and objects would pass the test.

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