12

I have arrays of deeply nested objects. I would like to write a function to extract arbitrary child objects from these arrays. In some cases values of nested properties are values and objects, in other cases they are arrays.

Examples of arrays are below:

[{parent: {level1: {level2: 'data'}}}]

[{parent: {level1: [{level2: {...}}, {level2: {...}}, {level2: {...}}]}}]

[{parent: {level1: [{level2: {level3: 'data'}}, {level2: {..}}, {level2: {..}}]}}]

Calling extraction function on such an array should result in an array of objects that we're interested in.

Examples of calling the function and its results for the example arrays above:

extractChildren(source, 'level2') = [{level2: 'data'}]

extractChildren(source, 'level2') = [{level2: {...}, level2: {...}, level2: {...}]

extractChildren(source, 'level3') = [{level3: 'data'}]

Is there a concise way to achieve this with lodash or I should use regular JavaScript to iterate through properties?

P.S. Think of it as equivalent of XPath select all nodes with the name "nodename"

krl
  • 5,087
  • 4
  • 36
  • 53

2 Answers2

1

I hope, it helps:

'use strict';

let _ = require("lodash");
let source = [{parent: {level1: [{level2: {level3: 'data'}}, {level2: {}}, {level2: {}}]}}];

function extractChildren(source, specKey) {
    let results = [];
    let search = function(source, specKey) {
        _.forEach(source, function(item) {
            if (!!item[specKey]) {
                let obj = {};
                obj[specKey] = item[specKey];
                results.push(obj);
                return;
            }

            search(item, specKey);
        });
    };

    search(source, specKey);
    return results;
};

console.log(extractChildren(source, 'level3'));
// [ { level3: 'data' } ]
Gergo
  • 2,190
  • 22
  • 24
  • Ends up with `Maximum call stack size exceeded` with default Node.js config and my actual data – krl Feb 02 '16 at 09:47
  • How big is your data? – Gergo Feb 02 '16 at 12:42
  • Up to 10 levels deep or so, but not very big (tens of KB), since otherwise I would use streams. Nevermind. I still need to approach the problem differently since I need to gather some data from parent objects also and such simple `extractChildren` function won't work for that case anyway. – krl Feb 02 '16 at 12:59
1

From this question:

Elegant:

function find_obj_by_name(obj, key) {
    if( !(obj instanceof Array) ) return [];

    if (key in obj)
        return [obj[key]];

    return _.flatten(_.map(obj, function(v) {
        return typeof v == "object" ? find_obj_by_name(v, key) : [];
    }), true);
}

Efficient:

function find_obj_by_name(obj, key) {
    if( !(obj instanceof Array) ) return [];

    if (key in obj)
        return [obj[key]];

    var res = [];
    _.forEach(obj, function(v) {
        if (typeof v == "object" && (v = find_obj_by_name(v, key)).length)
            res.push.apply(res, v);
    });
    return res;
}
Community
  • 1
  • 1
rphv
  • 5,409
  • 3
  • 29
  • 47
  • 1
    The "efficient" can be improved by using vanilla `forEach` instead of `_.forEach` – vsync Sep 29 '16 at 14:43
  • Also, I don't understand what is this `fn(v, key)`. did you test this? what is `fn`? – vsync Sep 29 '16 at 14:52
  • I now see you have copied another [answer](http://stackoverflow.com/a/15643385/104380), changed it, and didn't verify the result. – vsync Sep 29 '16 at 14:58
  • ...yes, I referenced the other answer in my post. Maybe you didn't see the link in the first line? I also tested this answer, so I'm not sure what you're asking. – rphv Sep 29 '16 at 15:33
  • You serous? `fn` is undefined, so how can you possibly claim you've tested it – vsync Sep 29 '16 at 15:38
  • Great catch! `fn` should have been `find_obj_by_name`. Many thanks for your diligent efforts. Note that a test on a non-nested object will succeed because it never hits the `forEach` condition. Maybe you can suggest some more thorough unit tests for this? – rphv Sep 29 '16 at 15:43
  • Maybe add `if( !(obj instanceof Array) ) return []` at the beginning, no? – vsync Sep 29 '16 at 15:54
  • I would even change the name from `obj` to `arr` so it would be clear that an array should be passed to the function. and also perhaps also change `v` to `obj` – vsync Sep 30 '16 at 21:29