39

I'm not seeing a way to find objects when my condition would involve a nested array.

var modules = [{
    name: 'Module1',
    submodules: [{
        name: 'Submodule1',
        id: 1
      }, {
        name: 'Submodule2',
        id: 2
      }
    ]
  }, {
    name: 'Module2',
    submodules: [{
        name: 'Submodule1',
        id: 3
      }, {
        name: 'Submodule2',
        id: 4
      }
    ]
  }
];

This won't work because submodules is an array, not an object. Is there any shorthand that would make this work? I'm trying to avoid iterating the array manually.

_.where(modules, {submodules:{id:3}});
helion3
  • 34,737
  • 15
  • 57
  • 100

6 Answers6

88

Lodash allows you to filter in nested data (including arrays) like this:

_.filter(modules, { submodules: [ { id: 2 } ]});

martinoss
  • 5,268
  • 2
  • 45
  • 53
  • 13
    You can use the exact same syntax with `_.find` as well. – Devon Sams Jun 20 '17 at 23:57
  • 2
    @DevonSams although `_.filter` returns the filtered array or an empty array and `_.find` returns the matched element which could be an object, array, number, string, boolean or undefined. – user115014 Jan 09 '18 at 09:37
  • 12
    Is there any way to just return that nested object instead of whole parent object? – Anurag pareek Mar 13 '19 at 06:33
  • how can I target an object inside an array of barcodes which is inside a sizes array and that sizes array is inside a colors array...How can I directly point to barcodes? – Ibad Shaikh Dec 08 '20 at 09:23
28

Here's what I came up with:

_.find(modules, _.flow(
    _.property('submodules'),
    _.partialRight(_.some, { id: 2 })
));
// → { name: 'Module1', ... }

Using flow(), you can construct a callback function that does what you need. When call, the data flows through each function. The first thing you want is the submodules property, and you can get that using the property() function.

The the submodules array is then fed into some(), which returns true if it contains the submodule you're after, in this case, ID 2.

Replace find() with filter() if you're looking for multiple modules, and not just the first one found.

Adam Boduch
  • 11,023
  • 3
  • 30
  • 38
  • I like it, thanks! Not quite as elegant as a dynamic path could be, but lodash doesn't support that. Maybe I'll make a mixin... – helion3 May 07 '15 at 22:17
  • 2
    FYI, in Lodash 4.0, they removed the .any() function. Now you would use .some(). – Justin Mar 31 '16 at 19:27
1

I think your best chance is using a function, for obtaining the module.

_.select(modules, function (module) {
  return _.any(module.submodules, function (submodule) {
    return _.where(submodule, {id:3});
  });
});

try this for getting the submodule

.where(.pluck(modules, "submodules"), {submodules:{id:3}});

Mabedan
  • 857
  • 8
  • 30
  • Good idea. I have to use `_flatten` in addition because `_.pluck` returns an array of arrays. – helion3 May 07 '15 at 17:13
  • (yes flatten came to my mind later), but i changed my answer completely, because it wouldn't work. hopeful thinking :) – Mabedan May 07 '15 at 17:17
  • 1
    But it did work. Using the flatten+pluck logic, my test works. http://jsbin.com/howazulexe/1/edit?js – helion3 May 07 '15 at 17:20
  • i thought the aim is to obtain the parent module, not the submodule. in that case then, yes :D – Mabedan May 07 '15 at 17:23
  • Oh, good thought. Not in my case, but that's an equally useful use-case. If you want to update your answer with both, I'll mark it accepted. I'm not seeing anything cleaner. Honestly, I'm surprised there's no matcher support. – helion3 May 07 '15 at 17:36
1

I looked into this and I think the best option is to use Deepdash. It's a collection of methods to do deeply filter, find etc.

Sure it would be possible with lodash alone but with Deepdash it's easier.

I tried to convert the previous answer to the latest Lodash version but that was not working. Every method was deprecated in v4 of Lodash. Possible replacements: select = map or filter, any = some, where = filter)

findDeep returns an object with some information to the found item (just some values, see the docs for more details):

  • value is the object found
  • key that's the index in the nested array
  • parent the parent of the value

So the code for the findDeep looks like:

const modules = [{
  name: 'Module1',
  submodules: [{
    name: 'Submodule1',
    id: 1
  }, {
    name: 'Submodule2',
    id: 2
  }]
}, {
  name: 'Module2',
  submodules: [{
    name: 'Submodule1',
    id: 3
  }, {
    name: 'Submodule2',
    id: 4
  }]
}];

const getModule = (modules, id) =>
  _.findDeep(modules, module => module.id === id, {
    childrenPath: "submodules"
  });


const resultEl = document.getElementById("result");
const foundModule = getModule(modules, 3).value;

resultEl.innerText = JSON.stringify(foundModule, null, 2);
<script src="https://cdn.jsdelivr.net/npm/deepdash/browser/deepdash.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash/lodash.min.js"></script>
<script>
  deepdash(_);
</script>

<h2>Example to get module with id = 3</h2>
<pre id="result"></pre>
AWolf
  • 8,770
  • 5
  • 33
  • 39
0

Another solution can be:

const var = _.find(modules, (item) => {
        return (item.submodules.id === 2)
      })

You can either user _.find to get the first occurrence or _.filter to get all ot them.

Joel Vega
  • 41
  • 3
0

If you want to search all by keyword:

const searchWord = 'Hello world'.trim().toLowerCase();
const result = JSON.stringify(object).trim().toLowerCase().includes(searchWord);
  1. Trim and lowercase search word(s).
  2. Stringify object, trim and lowercase.
  3. Check if it includes the search word(s).
NicolayM
  • 161
  • 1
  • 3