最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - A better vanilla JS approach to finding any false value in a nested object? - Stack Overflow

programmeradmin1浏览0评论

If I have an object, such as:

const obj = {
  field1: {
    subfield1: true,
    subfield2: true,
  },
  field2: {
    subfield3: true,
  },
  field3: {
    subfield4: false,
    subfield5: true,
  }
}

Then I can do a nested for loop to return true if a value is false by doing,

const findFalse = obj => {
  for (const field in obj) {
    for (const subfield in obj[field]) {
      if (!obj[field][subfield]) return true
    }
  }
}

If this object wasn't nested, it'd be a bit easier, I could do something like,

const findFalse = obj => Object.keys(obj).some(prop => !obj[prop]);

Both approaches don't really suit what I need, because the object can have 2, 3 or more nested properties. I've come up with a recursive way to figure this out, such as:

const isObj = obj => obj && obj.constructor === Object;

const findFalseRecursively = obj => {
  for (const field in obj) {
    if (isObj(obj[field]))
      return findFalseRecursively(obj[field])

    if (obj[field] === false)
      return true
  }
}

But I'm wondering if there's a cleaner or more efficient way of achieving this?

I would expect the function to return the first false value that it sees, and break out of the function/for loop instantly.

If I have an object, such as:

const obj = {
  field1: {
    subfield1: true,
    subfield2: true,
  },
  field2: {
    subfield3: true,
  },
  field3: {
    subfield4: false,
    subfield5: true,
  }
}

Then I can do a nested for loop to return true if a value is false by doing,

const findFalse = obj => {
  for (const field in obj) {
    for (const subfield in obj[field]) {
      if (!obj[field][subfield]) return true
    }
  }
}

If this object wasn't nested, it'd be a bit easier, I could do something like,

const findFalse = obj => Object.keys(obj).some(prop => !obj[prop]);

Both approaches don't really suit what I need, because the object can have 2, 3 or more nested properties. I've come up with a recursive way to figure this out, such as:

const isObj = obj => obj && obj.constructor === Object;

const findFalseRecursively = obj => {
  for (const field in obj) {
    if (isObj(obj[field]))
      return findFalseRecursively(obj[field])

    if (obj[field] === false)
      return true
  }
}

But I'm wondering if there's a cleaner or more efficient way of achieving this?

I would expect the function to return the first false value that it sees, and break out of the function/for loop instantly.

Share Improve this question edited Jul 22, 2020 at 9:21 Mike K asked Jul 22, 2020 at 9:17 Mike KMike K 6,49117 gold badges75 silver badges143 bronze badges 5
  • do you want a single true/false? or all with false? please add the wanted result. – Nina Scholz Commented Jul 22, 2020 at 9:18
  • @NinaScholz He never mentions that he wants all the objects with false, just that he wants to detect, whether an object contains a value that is false. So my guess is: on the first false, return true. – MauriceNino Commented Jul 22, 2020 at 9:21
  • I've clarified the question – Mike K Commented Jul 22, 2020 at 9:21
  • Here's a JSPerf. – Moritz Roessler Commented Jul 22, 2020 at 9:49
  • Is your recursion correct? Will it misfire on your first example? Does it need to return false at some point? – Teepeemm Commented Jul 22, 2020 at 17:43
Add a comment  | 

4 Answers 4

Reset to default 13

You could take the valus from the object and iterate with a short circuit.

const 
    hasTrue = object => Object
        .values(object)
        .some(v => v === false || v && typeof v === 'object' && hasTrue(v)),
    object = { field1: { subfield1: true, subfield2: true }, field2: { subfield3: true }, field3: { subfield4: false, subfield5: true } };

console.log(hasTrue(object));

With suggested check object in advance

const 
    hasTrue = value => value === true || !!value && typeof value === 'object' && Object
        .values(value)
        .some(hasTrue),
    object = { field1: { subfield1: true, subfield2: true }, field2: { subfield3: true }, field3: { subfield4: false, subfield5: true } };

console.log(hasTrue(object));

Here is a simple recursive solution that uses Object.values,flat and some.

function toValues (obj) {
    if (typeof obj === 'object') return Object.values(obj).map(toValues);
    return obj;
}
const findFalse = obj => toValues(obj).flat().some(v => v === false)

const res = findFalse(obj);

console.log (res);
<script>
const obj = {
  field1: {
    subfield1: true,
    subfield2: true,
  },
  field2: {
    subfield3: true,
  },
  field3: {
    subfield4: false,
    subfield5: true,
  }
}
</script>

You could do it by searching inside the string representation of the object like so:

const o = {
  field1: {
    subfield1: true,
    subfield2: true,
  },
  field2: {
    subfield3: true,
  },
  field3: {
    subfield4: false,
    subfield5: true,
  }
};

const containsFalse = (obj) => /\".+\"\:false[^\"]/.test(JSON.stringify(obj));

console.log(containsFalse(o));
console.log(containsFalse({a:":false"}));

JSON.stringify(...) is pretty costly though, so I don't know if this is an improvement performance wise.

Update: It is a bit slower than other methods shown here, but if you need something simple it might still be useful. https://jsbench.me/4akcx6gxsn/1

If there's arbitrary nesting, you can't avoid recursion. You can make it cleaner though:

const isObj = obj => obj && typeof obj == "object";
const findFalseRecursively = val => {
  if (val === false)
    return true;
  if (isObj(val))
    for (const field in val) {
      if (findFalseRecursively(val[field]))
        return true;
  return false;
}
发布评论

评论列表(0)

  1. 暂无评论