1

I have a JSON object that contains an array of objects. I also have an array of desired values, and I want to search for these values inside my JSON. I only care about the first match. If no match is found, throw an error.

There might be a better way to do this but this is what I came up with:

function myFunction() {
    $.getJSON('database.json')
    .done(db => {
        for (let i = 0; i < desiredValues.length; i++) {
            db.arrayOfObjects.forEach(object => {
                if (object.propertyValue === desiredValues[i]) {
                    console.log("Match found!");
                    return; // break out of myFunction()
                }
            });
        }
        throw Error("Match not found.");
    })
    .fail(error => {
        throw Error("getJSON request failed.\n" + error);
    })
}

My problem is that the return statement only breaks out of the current iteration of forEach (why?). The remaining objects are still tested for all remaining values of desiredValues, and the error is always thrown. How can I exit myFunction() entirely when a match is found, or how can I restructure this function to achieve what I want?

Edit: I probably should have mentioned I also need to do stuff with the object that was matched, so not just return true if there is a match.

Matheus Leão
  • 417
  • 4
  • 12

2 Answers2

2

The problem with return inside forEach is that it terminates the current forEach callback only - that's what return does, it stops the current execution of a function and (possibly) returns a value, without affecting possible future calls of that function.

Use two nested Array.prototype.some instead, which will break out of both immediately and return true once a match is found, and iterate completely through and return false otherwise:

const isFound = desiredValues.some(valueToFind => (
  db.arrayOfObjcts.some(({ propertyValue }) => (
    object.propertyValue === valueToFind
  ))
));
if (!isFound) {
  throw new Error('Match not found');
}

Or, if you're not comfortable with destructuring arguments:

const isFound = desiredValues.some(valueToFind => (
  db.arrayOfObjcts.some(object => (
    propertyValue === valueToFind
  ))
));
if (!isFound) {
  throw new Error('Match not found');
}

To identify which value was found, use .find instead for the outer loop:

const valueFound = desiredValues.find(valueToFind => (
  db.arrayOfObjcts.some(({ propertyValue }) => (
    object.propertyValue === valueToFind
  ))
));
if (!valueFound) {
  throw new Error('Match not found');
}
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • Just to answer the minor question the OP asked for their original code, the return statement only breaks out of the current iteration of forEach becuase that return statement is inside of the callback that is invoked during every forEach iteration. – pl61 Mar 24 '19 at 01:30
  • What if I want to identify which object has the matched value? Can I just use .find in the inner loop? – Matheus Leão Mar 24 '19 at 01:58
  • @MatheusLeão Yes - use `.find` for the inner loop, and assign the result to an outer variable *while still inside the outer loop*. – CertainPerformance Mar 24 '19 at 02:12
1

Simply use .find on array.

function myFunction() {
    $.getJSON('database.json')
      .done(db => {
        for (let i = 0; i < desiredValues.length; i++) {
          if (
            db.arrayOfObjects
              .find(object => object.propertyValue === desiredValues[i])
            !== undefined
          ) {
            console.log("Found match! Matched value:", desiredValues[i]);
            return;
          }
        throw Error("Match not found.");
      })
      .fail(error => {
        throw Error("getJSON request failed.\n" + error);
      })
}

or map array of objects and mix .find with .includes:

function myFunction() {
    $.getJSON('database.json')
      .done(db => {
        const values = db.arrayOfObjects.map(object => object.propertyValue);

        const match = values.find(value => desiredValues.includes(value)); 

        if (match !== undefined) {
          console.log("Found match! Matched value:", match);
          return;
        }

        throw Error("Match not found.");
      })
      .fail(error => {
        throw Error("getJSON request failed.\n" + error);
      })
}

or .filter it to get all matched values:

function myFunction() {
    $.getJSON('database.json')
      .done(db => {
        const values = db.arrayOfObjects.map(object => object.propertyValue);

        const matches = values.filter(value => desiredValues.includes(value)); 

        if (matches.length) {
          console.log("Found", matches.length, "matches! Matched values:", matches);
          return;
        }

        throw Error("Match not found.");
      })
      .fail(error => {
        throw Error("getJSON request failed.\n" + error);
      })
}
num8er
  • 18,604
  • 3
  • 43
  • 57
  • In the second example, if there is a match to a value of zero, won't the function still throw an error? This seems to do exactly what I need, except that if statement might need to be a little more specific. – Matheus Leão Mar 24 '19 at 02:31
  • @MatheusLeão thank You, I forgot to check it when was trying to minimize code and make it more readable – num8er Mar 24 '19 at 10:35