4

I have javascript objects which looks similar to this:

{
  id: 43,
  name: 'ajajaj'
  nestedObj1: {
    id: 53,
    name: 'ababab'
    foo: 'xxxx'
    nestedObj2: {
      id: 90,
      name: 'akakaka'
      foo2: 'sdsddd'
      surname: 'sdadasd'
      nestedObj3: {
        id: ..
        name: ..
        ...
      },
      objectsArr: [
        {id: .., name: ..., nestedOb4: {...} },
        {id: .., name: ..., nestedOb4: {...} }
      ]
    },
    foo0: ...
  }
  name: 'blabla'
}

Each objects which I have, has property id and name. My goal is pick all properties in first level, so properties of object with id 43 I pick all, and from every nested objects, I just pick properties id and name, and another will be thrown away.

I am using in project lodash, so I found function pick, but I don't know how to use this function for nested objects. Also note that nested objects could be also in array. Exist some elegant way for that please? Thanks in advice.

2 Answers 2

8

pick function of lodash is able to fetch nested property like so:

_.pick(obj, [
  'id',
  'name',
  'nestedObj1.id',
  'nestedObj1.name',
  'nestedObj1.nestedObj2.id',
  'nestedObj1.nestedObj2.name',
  'nestedObj1.nestedObj2.nestedObj3.id',
  'nestedObj1.nestedObj2.nestedObj3.name',
  'nestedObj1.nestedObj2.objectsArr[0].id',
  'nestedObj1.nestedObj2.objectsArr[0].name',
  'nestedObj1.nestedObj2.objectsArr[0].nestedObj4.id',
  'nestedObj1.nestedObj2.objectsArr[0].nestedObj4.name',
  'nestedObj1.nestedObj2.objectsArr[1].id',
  'nestedObj1.nestedObj2.objectsArr[1].name',
  'nestedObj1.nestedObj2.objectsArr[1].nestedObj4.id',
  'nestedObj1.nestedObj2.objectsArr[1].nestedObj4.name',
])

The problem is, you must know the array length to pick all objects in them.

To mitigate this, you can use this function to iterate over arrays:

const _ = require('lodash')

const pickExtended = (object, paths) => {
  return paths.reduce((result, path) => {
    if (path.includes("[].")) {
      const [collectionPath, itemPath] = path.split(/\[]\.(.+)/);
      const collection = _.get(object, collectionPath);

      if (!_.isArray(collection)) {
        return result;
      }

      const partialResult = {};
      _.set(
        partialResult,
        collectionPath,
        _.map(collection, item => pickExtended(item, [itemPath]))
      );

      return _.merge(result, partialResult);
    }

    return _.merge(result, _.pick(object, [path]));
  }, {});
};

You can then get the result you want with:

pickExtended(obj, [
  'id',
  'name',
  'nestedObj1.id',
  'nestedObj1.name',
  'nestedObj1.nestedObj2.id',
  'nestedObj1.nestedObj2.name',
  'nestedObj1.nestedObj2.nestedObj3.id',
  'nestedObj1.nestedObj2.nestedObj3.name',
  'nestedObj1.nestedObj2.objectsArr[].id',
  'nestedObj1.nestedObj2.objectsArr[].name',
  'nestedObj1.nestedObj2.objectsArr[].nestedObj4.id',
  'nestedObj1.nestedObj2.objectsArr[].nestedObj4.name',
])

Here is the associated mocha test:

describe("pickExtended", () => {
  const object = {
    a: 1,
    b: "2",
    c: { d: 3, e: 4 },
    f: [
      { a: 11, b: 12, c: [{ d: 21, e: 22 }, { d: 31, e: 32 }] },
      { a: 13, b: 14, c: [{ d: 23, e: 24 }, { d: 33, e: 34 }] }
    ],
    g: [],
    h: [{ a: 41 }, { a: 42 }, { a: 43 }]
  };

  it("should pick properties in nested collection", () => {
    expect(
      pickExtended(object, ["a", "c.d", "f[].c[].d", "g[].a", "b[].a", "h[]"])
    ).to.deep.equal({
      a: 1,
      c: { d: 3 },
      f: [{ c: [{ d: 21 }, { d: 31 }] }, { c: [{ d: 23 }, { d: 33 }] }],
      g: []
    });
  });
});

Keep in mind that no performance test was done on this. It might get slow for big object.

1

You can do this using recursion, as long as your objects do have an end to the levels of nesting. Otherwise, you run the risk of causing a stack limit error.

function pickProperties(obj) {
    var retObj = {};
    Object.keys(obj).forEach(function (key) {
        if (key === 'id' || key === 'name') {
            retObj[key] = obj[key];
        } else if (_.isObject(obj[key])) {
            retObj[key] = pickProperties(obj[key]);
        } else if (_.isArray(obj[key])) {
            retObj[key] = obj[key].map(function(arrayObj) {
                return pickProperties(arrayObj);
            });
        }
    });
    return retObj;
}
0

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.