114

I have to remove unwanted object properties that do not match my model. How can I achieve it with Lodash?

My model is:

var model = {
   fname: null,
   lname: null
}

My controller output before sending to the server will be:

var credentials = {
    fname: "xyz",
    lname: "abc",
    age: 23
}

I am aware I can use

delete credentials.age

but what if I have lots of unwanted properties? Can I achieve it with Lodash?

Phil Dukhov
  • 67,741
  • 15
  • 184
  • 220
Alaksandar Jesus Gene
  • 6,523
  • 12
  • 52
  • 83

12 Answers12

230

You can approach it from either an "allow list" or a "block list" way:

// Block list
// Remove the values you don't want
var result = _.omit(credentials, ['age']);

// Allow list
// Only allow certain values
var result = _.pick(credentials, ['fname', 'lname']);

If it's reusable business logic, you can partial it out as well:

// Partial out a "block list" version
var clean = _.partial(_.omit, _, ['age']);

// and later
var result = clean(credentials);

Note that Lodash 5 will drop support for omit

A similar approach can be achieved without Lodash:

const transform = (obj, predicate) => {
    return Object.keys(obj).reduce((memo, key) => {
    if(predicate(obj[key], key)) {
        memo[key] = obj[key]
    }
    return memo
    }, {})
}

const omit = (obj, items) => transform(obj, (value, key) => !items.includes(key))

const pick = (obj, items) => transform(obj, (value, key) => items.includes(key))

// Partials
// Lazy clean
const cleanL = (obj) => omit(obj, ['age'])

// Guarded clean
const cleanG = (obj) => pick(obj, ['fname', 'lname'])


// "App"
const credentials = {
    fname:"xyz",
    lname:"abc",
    age:23
}

const omitted = omit(credentials, ['age'])
const picked = pick(credentials, ['age'])
const cleanedL = cleanL(credentials)
const cleanedG = cleanG(credentials)
Gandalf
  • 2,921
  • 5
  • 31
  • 44
Chris
  • 54,599
  • 30
  • 149
  • 186
  • 4
    this is the correct answer since the function that removes is `_.omit` while `_.pick` specifies which you want (which is quite the opposite) – thiagoh Jan 11 '18 at 17:33
  • correct - and depends on your general approach and surrounding business logic - either version will work depending on your requirements – Chris Jan 11 '18 at 23:00
  • For the partial you made a typo, its `_.partial(_.omit ...`. – Bernardo Dal Corno Feb 19 '18 at 19:24
  • 3
    The blacklist IS the answer expected for this question. Should be the right answer – Bernardo Dal Corno Feb 19 '18 at 19:25
  • 2
    **WARNING:** - [omit is slated to be removed in version 5](https://github.com/lodash/lodash/wiki/Roadmap) (see the [discussion here](https://github.com/lodash/lodash/issues/2930)) – random_user_name Jun 25 '19 at 15:16
  • Very interesting. I will update the answer to mark it for lodash4, then revisit once lodash5 is officially released – Chris Jun 25 '19 at 22:59
  • Thanks @cale_b I've reworked the answer to include a version with lodash _omitted_ – Chris Jun 25 '19 at 23:29
  • @Chris - sorry for the confusion - they recommend `_.pick` over `_.omit`, with some decent reasoning... so I was just suggesting using pick would be preferred for future compatibility... :) – random_user_name Jun 26 '19 at 14:40
  • @cale_b Thanks, you explained it perfectly. I figured because I would have to write something to replace omit I might as well show an example of doing it without Lodash altogether. I love Lodash but if omit is being removed, might as well go with a lighter alternative of omit – Chris Jun 26 '19 at 22:51
  • If anyone is interested in the edits to this question please see: https://meta.stackexchange.com/questions/350880/what-is-stack-exchanges-official-stance-on-words-such-as-black-list-white-l – Chris Jul 23 '20 at 13:00
  • Someone knows why they're going to remove `_.omit`? `_.omit` and `_.pick` serve different purposes... – jperl May 22 '23 at 12:09
79

Get a list of properties from model using _.keys(), and use _.pick() to extract the properties from credentials to a new object:

var model = {
   fname:null,
   lname:null
};

var credentials = {
    fname:"xyz",
    lname:"abc",
    age:23
};

var result = _.pick(credentials, _.keys(model));

console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.16.4/lodash.min.js"></script>

If you don't want to use Lodash, you can use Object.keys(), and Array.prototype.reduce():

var model = {
   fname:null,
   lname:null
};

var credentials = {
    fname:"xyz",
    lname:"abc",
    age:23
};

var result = Object.keys(model).reduce(function(obj, key) {
  obj[key] = credentials[key];
  return obj;
}, {});

console.log(result);
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Ori Drori
  • 183,571
  • 29
  • 224
  • 209
  • how can we achieve the same if the object is nested, for ex const obj = { a: {b: 1, c: 2}}, i want to remove only c, expected output should be. {a: {b:1}} – Deekshith MR Jul 18 '23 at 11:13
9

You can easily do this using _.pick:

var model = {
  fname: null,
  lname: null
};

var credentials = {
  fname: 'abc',
  lname: 'xyz',
  age: 2
};

var result = _.pick(credentials, _.keys(model));


console.log('result =', result);
<script src="https://cdn.jsdelivr.net/lodash/4.16.4/lodash.min.js"></script>

But you can simply use pure JavaScript (specially if you use ECMAScript 6), like this:

const model = {
  fname: null,
  lname: null
};

const credentials = {
  fname: 'abc',
  lname: 'xyz',
  age: 2
};

const newModel = {};

Object.keys(model).forEach(key => newModel[key] = credentials[key]);

console.log('newModel =', newModel);
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
6

Lodash unset is suitable for removing a few unwanted keys.

const myObj = {
    keyOne: "hello",
    keyTwo: "world"
}

unset(myObj, "keyTwo");

console.log(myObj); /// myObj = { keyOne: "hello" }
yungy
  • 61
  • 1
  • 2
4

Here I have used omit() for the respective 'key' which you want to remove... by using the Lodash library:

var credentials = [{
        fname: "xyz",
        lname: "abc",
        age: 23
}]

let result = _.map(credentials, object => {
                       return _.omit(object, ['fname', 'lname'])
                   })

console.log('result', result)
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Anupam Maurya
  • 1,927
  • 22
  • 26
  • 3
    be careful when you want to use `omit`, I used to like `omit` before but now it got `removed` : https://github.com/lodash/lodash/issues/2930 – Chau Giang Mar 28 '19 at 06:46
1

You can use _.omit() for emitting the key from a JSON array if you have fewer objects:

_.forEach(data, (d) => {
    _.omit(d, ['keyToEmit1', 'keyToEmit2'])
});

If you have more objects, you can use the reverse of it which is _.pick():

_.forEach(data, (d) => {
    _.pick(d, ['keyToPick1', 'keyToPick2'])
});
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
0

To select (or remove) object properties that satisfy a given condition deeply, you can use something like this:

function pickByDeep(object, condition, arraysToo=false) {
  return _.transform(object, (acc, val, key) => {
    if (_.isPlainObject(val) || arraysToo && _.isArray(val)) {
      acc[key] = pickByDeep(val, condition, arraysToo);
    } else if (condition(val, key, object)) {
      acc[key] = val;
    }
  });
}

https://codepen.io/aercolino/pen/MWgjyjm

aercolino
  • 2,193
  • 1
  • 22
  • 20
0

This is my solution to deep remove empty properties with Lodash:

const compactDeep = obj => {
    const emptyFields = [];

    function calculateEmpty(prefix, source) {
        _.each(source, (val, key) => {
           if (_.isObject(val) && !_.isEmpty(val)) {
                calculateEmpty(`${prefix}${key}.`, val);
            } else if ((!_.isBoolean(val) && !_.isNumber(val) && !val) || (_.isObject(val) && _.isEmpty(val))) {
                emptyFields.push(`${prefix}${key}`);
            }
        });
    }

    calculateEmpty('', obj);

    return _.omit(obj, emptyFields);
};
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
iliya.rudberg
  • 739
  • 12
  • 23
  • 1
    What is the idea? What does your solution do? – Peter Mortensen Aug 09 '20 at 18:08
  • -1; this isn't useful for solving the example problem the asker gave, nor for the generic problem of removing properties from an object; it's a solution to a weirdly arbitrary problem that isn't the one asked and that isn't spelt out in this answer. It's also not idempotent (`compactDeep({a: {b: []}})` gives a different result to `compactDeep(compactDeep({a: {b: []}}))`, which feels like a bug, though one can't really say so objectively since it's not spelt out what this is meant to do. – Mark Amery Jul 03 '21 at 16:46
0

For array of objects

model = _.filter(model, a => {
          if (!a.age) { return a }
        })
m4n0
  • 29,823
  • 27
  • 76
  • 89
  • 3
    Welcome to Stack Overflow, and thank you for contributing an answer. Would you kindly edit your answer to to include an explanation of your code? That will help future readers better understand what is going on, and especially those members of the community who are new to the language and struggling to understand the concepts. That's especially important when there's already an answer from five years ago that's been validated by the community with 178 upvotes to date. Under what conditions might your approach be preferred? Are you taking advantage of new capabilities? – Jeremy Caney Sep 21 '21 at 00:59
0

Recursively removing paths.

I just needed something similar, not removing just keys, but keys by with paths recursively.

Thought I'd share.

Simple readable example, no dependencies

/**
 * Removes path from an object recursively.
 * A full path to the key is not required.
 * The original object is not modified.
 *
 * Example:
 *   const original = { a: { b: { c: 'value' } }, c: 'value'  }
 *
 *   omitPathRecursively(original, 'a') // outputs: { c: 'value' }
 *   omitPathRecursively(original, 'c') // outputs: { a: { b: {} } }
 *   omitPathRecursively(original, 'b.c') // { a: { b: {} }, c: 'value' }
 */
export const omitPathRecursively = (original, path, depth = 1) => {
  const segments = path.split('.')
  const final = depth === segments.length

  return JSON.parse(
    JSON.stringify(original, (key, value) => {
      const match = key === segments[depth - 1]

      if (!match) return value
      if (!final) return omitPathRecursively(value, path, depth + 1)
      return undefined
    })
  )
}

Working example: https://jsfiddle.net/webbertakken/60thvguc/1/

Webber
  • 4,672
  • 4
  • 29
  • 38
0

While looking for a solution that would work for both arrays and objects, I didn't find one and so I created it.

/**
 * Recursively ignore keys from array or object
 */
const ignoreKeysRecursively = (obj, keys = []) => {
  const keyIsToIgnore = (key) => {
    return keys.map((a) => a.toLowerCase()).includes(key)
  }

  const serializeObject = (item) => {
    return Object.fromEntries(
      Object.entries(item)
        .filter(([key, value]) => key && value)
        .reduce((prev, curr, currIndex) => {
          if (!keyIsToIgnore(curr[0]))
            prev[currIndex] =
              [
                curr[0],
                // serialize array
                Array.isArray(curr[1])
                  ? // eslint-disable-next-line
                  serializeArray(curr[1])
                  : // serialize object
                  !Array.isArray(curr[1]) && typeof curr[1] === 'object'
                  ? serializeObject(curr[1])
                  : curr[1],
              ] || []
          return prev
        }, []),
    )
  }

  const serializeArray = (item) => {
    const serialized = []
    for (const entry of item) {
      if (typeof entry === 'string') serialized.push(entry)
      if (typeof entry === 'object' && !Array.isArray(entry)) serialized.push(serializeObject(entry))
      if (Array.isArray(entry)) serialized.push(serializeArray(entry))
    }
    return serialized
  }

  if (Array.isArray(obj)) return serializeArray(obj)
  return serializeObject(obj)
}

// usage
const refObject = [{name: "Jessica", password: "ygd6g46"}]
// ignore password
const obj = ignoreKeysRecursively(refObject, ["password"])
// expects returned array to only have name attribute
console.log(obj)
Danny Sofftie
  • 1,031
  • 1
  • 8
  • 16
0

let asdf = [{"asd": 12, "asdf": 123}, {"asd": 121, "asdf": 1231}, {"asd": 142, "asdf": 1243}]

asdf = _.map(asdf, function (row) { return _.omit(row, ['asd']) })

Anupam Maurya
  • 1,927
  • 22
  • 26