Christian Schlensker
Christian Schlensker

Reputation: 22478

How to get a subset of a javascript object's properties

Say I have an object:

elmo = { 
  color: 'red',
  annoying: true,
  height: 'unknown',
  meta: { one: '1', two: '2'}
};

I want to make a new object with a subset of its properties.

 // pseudo code
 subset = elmo.slice('color', 'height')

 //=> { color: 'red', height: 'unknown' }

How may I achieve this?

Upvotes: 805

Views: 582394

Answers (29)

Timar Ivo Batis
Timar Ivo Batis

Reputation: 1996

To add another esoteric way, this works aswell:

var obj = {a: 1, b:2, c:3}
var newobj = ({a,c}=obj) && {a,c}
// {a: 1, c:3}

but you have to write the prop names twice.

Upvotes: 2

Christian Vincenzo Traina
Christian Vincenzo Traina

Reputation: 10384

Many answers I saw don't work with array of strings or they are way too much verbose.

I propose this solution that is a potential alternative to the lodash pick method but it's just one line long.

First thing define a pick list:

const pickList = ['color', 'height']

Then use:

pickList.reduce((acc, cur) => ({...acc, [cur]: elmo[cur]}), {})

Upvotes: 1

nemesit
nemesit

Reputation: 448

If you're looking to extract specific properties from an object into variables and simultaneously create a subset of that object you can use the following:

const theObject = { a: 1, b: 2, c: 3, d: 4, e: 5 };
const subset = { ...({ a, c, e} = new Proxy({}, { get: (t, p) => (t[p] = theObject[p], t[p]) })) }
console.log(a)      // 1
console.log(subset) // {a: 1, c: 3, e: 5}

This works because the get() trap of the proxy will record only the properties accessed in the destructuring { a, c, e} = proxy so { ...proxy } will contain only that subset of theObject

fiddle

Upvotes: 1

alex
alex

Reputation: 490183

There is nothing like that built-in to the core library, but you can use object destructuring to do it...

const {color, height} = sourceObject;
const newObject = {color, height};

You could also write a utility function do it...

const cloneAndPluck = function(sourceObject, keys) {
    const newObject = {};
    keys.forEach(key => { newObject[key] = sourceObject[key]; });
    return newObject;
};

const subset = cloneAndPluck(elmo, ["color", "height"]);

Libraries such as Lodash also have _.pick().

Upvotes: 58

Code Maniac
Code Maniac

Reputation: 37755

I am adding this answer because none of the answers used Comma operator.

It's very easy with destructuring assignment and the , operator:

const object = { a: 5, b: 6, c: 7  };
const picked = ({a,c} = object, {a,c})

console.log(picked);

Upvotes: 65

Estus Flask
Estus Flask

Reputation: 222369

Two common approaches are destructuring and conventional Lodash-like pick/omit implementation. The major practical difference between them is that destructuring requires a list of keys to be static, can't omit them, includes non-existent picked keys, i.e. it's inclusive. This may or not be desirable and cannot be changed for destructuring syntax.

Given:

var obj = { 'foo-bar': 1, bar: 2, qux: 3 };

The expected result for regular picking of foo-bar, bar, baz keys:

{ 'foo-bar': 1, bar: 2 }

The expected result for inclusive picking:

{ 'foo-bar': 1, bar: 2, baz: undefined }

Destructuring

Destructuring syntax allows to destructure and recombine an object, with either function parameters or variables.

The limitation is that a list of keys is predefined, they cannot be listed as strings, as described in the question. Destructuring becomes more complicated if a key is non-alphanumeric, e.g. foo-bar.

The upside is that it's performant solution that is natural to ES6.

The downside is that a list of keys is duplicated, this results in verbose code in case a list is long. Since destructuring duplicates object literal syntax in this case, a list can be copied and pasted as is.

IIFE

const subset = (({ 'foo-bar': foo, bar, baz }) => ({ 'foo-bar': foo, bar, baz }))(obj);

Temporary variables

Can cause the collision of variable names in current scope:

const { 'foo-bar': foo, bar, baz } = obj;
const subset = { 'foo-bar': foo, bar, baz };

Block-level scope can be used to avoid this:

let subset;
{
  const { 'foo-bar': foo, bar, baz } = obj;
  subset = { 'foo-bar': foo, bar, baz };
}

A list of strings

Arbitrary list of picked keys consists of strings, as the question requires. This allows to not predefine them and use variables that contain key names, ['foo-bar', someKey, ...moreKeys].

ECMAScript 2017 has Object.entries and Array.prototype.includes, ECMAScript 2019 has Object.fromEntries, they can be polyfilled when needed.

One-liners

Considering that an object to pick contains extra keys, it's generally more efficient to iterate over keys from a list rather than object keys, and vice versa if keys need to be omitted.

Pick (ES5)

var subset = ['foo-bar', 'bar', 'baz']
.reduce(function (obj2, key) {
  if (key in obj) // line can be removed to make it inclusive
    obj2[key] = obj[key];
  return obj2;
}, {});

Omit (ES5)

var subset = Object.keys(obj)
.filter(function (key) { 
  return ['baz', 'qux'].indexOf(key) < 0;
})
.reduce(function (obj2, key) {
  obj2[key] = obj[key];
  return obj2;
}, {});

Pick (ES6)

const subset = ['foo-bar', 'bar', 'baz']
.filter(key => key in obj) // line can be removed to make it inclusive
.reduce((obj2, key) => (obj2[key] = obj[key], obj2), {});

Omit (ES6)

const subset = Object.keys(obj)
.filter(key => ['baz', 'qux'].indexOf(key) < 0)
.reduce((obj2, key) => (obj2[key] = obj[key], obj2), {});

Pick (ES2019)

const subset = Object.fromEntries(
  ['foo-bar', 'bar', 'baz']
  .filter(key => key in obj) // line can be removed to make it inclusive
  .map(key => [key, obj[key]])
);

Omit (ES2019)

const subset = Object.fromEntries(
  Object.entries(obj)
  .filter(([key]) => !['baz', 'qux'].includes(key))
);

Reusable functions

One-liners can be represented as reusable helper functions similar to Lodash pick or omit, where a list of keys is passed through arguments, pick(obj, 'foo-bar', 'bar', 'baz').

JavaScript

const pick = (obj, ...keys) => Object.fromEntries(
  keys
  .filter(key => key in obj)
  .map(key => [key, obj[key]])
);

const inclusivePick = (obj, ...keys) => Object.fromEntries(
  keys.map(key => [key, obj[key]])
);

const omit = (obj, ...keys) => Object.fromEntries(
  Object.entries(obj)
  .filter(([key]) => !keys.includes(key))
);

TypeScript

Credit goes to @Claude.

const pick = <T extends {}, K extends keyof T>(obj: T, ...keys: K[]) => (
  Object.fromEntries(
    keys
    .filter(key => key in obj)
    .map(key => [key, obj[key]])
  ) as Pick<T, K>
);

const inclusivePick = <T extends {}, K extends (string | number | symbol)>(
  obj: T, ...keys: K[]
) => (
  Object.fromEntries(
    keys
    .map(key => [key, obj[key as unknown as keyof T]])
  ) as {[key in K]: key extends keyof T ? T[key] : undefined}
)

const omit = <T extends {}, K extends keyof T>(
  obj: T, ...keys: K[]
) =>(
  Object.fromEntries(
    Object.entries(obj)
    .filter(([key]) => !keys.includes(key as K))
  ) as Omit<T, K>
)

Upvotes: 355

Mark Swardstrom
Mark Swardstrom

Reputation: 18080

Worth noting a Zod schema will strip out unknown properties by default. If you're already using Zod, this likely fits right into your development process.

https://github.com/colinhacks/zod

import { z } from "zod";

// muppet schema
const muppet = z.object({
  color: z.string(),
  annoying: z.boolean(),
  height: z.string(),
  meta: z.object({ one: z.string(), two: z.string() }),
});

// TypeScript type if you want it
type TMuppet = z.infer<typeof muppet>;

// elmo example
const elmo: TMuppet = {
  color: "red",
  annoying: true,
  height: "unknown",
  meta: { one: "1", two: "2" },
};

// get a subset of the schema (another schema) if you want
const subset = muppet.pick({ color: true, height: true });

// parsing removes unknown properties by default
subset.parse(elmo); // { color: 'red', height: 'unknown' }

Upvotes: 0

If you want to keep more properties than the ones you want to remove, you could use the rest parameter syntax:

const obj = {
  a:1,
  b:2,
  c:3,
  d:4
};
const { a, ...newObj } = obj;
console.log(newObj); // {b: 2, c: 3, d: 4}

Upvotes: 5

Kyle Zimmer
Kyle Zimmer

Reputation: 310

Like several on this thread I agree with evert that the most obvious old school way of doing this is actually the best available, however for fun let me provide one other inadvisable way of doing it in certain circumstances, say when you already have your subset defined and you want to copy properties to it from another object that contains a superset or intersecting set of its properties.

let set = { a : 1, b : 2, c : 3 };
let subset = { a : null, b : null };
try {
  Object.assign(Object.seal(subset), set);
} catch (e) {
  console.log('its ok I meant to do that <(^.^)^');
}
console.log(subset);

Upvotes: 2

Costantin
Costantin

Reputation: 2656

The easiest way I found, which doesn't create unnecessary variables, is a function you can call and works identically to lodash is the following:

pick(obj, keys){
    return  Object.assign({}, ...keys.map(key => ({ [key]: obj[key] })))
}

For example:

pick(obj, keys){
    return  Object.assign({}, ...keys.map(key => ({ [key]: obj[key] })))
}
const obj = {a:1, b:2, c:3, d:4}
const keys = ['a', 'c', 'f']
const picked = pick(obj,keys)
console.log(picked)

pick = (obj, keys) => {
  return Object.assign({}, ...keys.map(key => ({
    [key]: obj[key]
  })))
}

const obj = {
  a: 1,
  b: 2,
  c: 3,
  d: 4
}
const keys = ['a', 'c', 'f']

const picked = pick(obj, keys)
console.log(picked)

Upvotes: 7

ADJenks
ADJenks

Reputation: 3424

Using the "with" statement with shorthand object literal syntax

Nobody has demonstrated this method yet, probably because it's terrible and you shouldn't do it, but I feel like it has to be listed.

var o = {a:1,b:2,c:3,d:4,e:4,f:5}
with(o){
  var output =  {a,b,f}
}
console.log(output)

Pro: You don't have to type the property names twice.

Cons: The "with" statement is not recommended for many reasons.

Conclusion: It works great, but don't use it.

Upvotes: 5

mpen
mpen

Reputation: 282845

TypeScript solution:

function pick<T extends object, U extends keyof T>(
  obj: T,
  paths: Array<U>
): Pick<T, U> {
  const ret = Object.create(null);
  for (const k of paths) {
    ret[k] = obj[k];
  }
  return ret;
}

The typing information even allows for auto-completion:

Credit to DefinitelyTyped for U extends keyof T trick!

TypeScript Playground

Upvotes: 37

iedmrc
iedmrc

Reputation: 752

I want to mention that very good curation here:

pick-es2019.js

Object.fromEntries(
  Object.entries(obj)
  .filter(([key]) => ['whitelisted', 'keys'].includes(key))
);

pick-es2017.js

Object.entries(obj)
.filter(([key]) => ['whitelisted', 'keys'].includes(key))
.reduce((obj, [key, val]) => Object.assign(obj, { [key]: val }), {});

pick-es2015.js

Object.keys(obj)
.filter((key) => ['whitelisted', 'keys'].indexOf(key) >= 0)
.reduce((newObj, key) => Object.assign(newObj, { [key]: obj[key] }), {})

omit-es2019.js

Object.fromEntries(
  Object.entries(obj)
  .filter(([key]) => !['blacklisted', 'keys'].includes(key))
);

omit-es2017.js

Object.entries(obj)
.filter(([key]) => !['blacklisted', 'keys'].includes(key))
.reduce((obj, [key, val]) => Object.assign(obj, { [key]: val }), {});

omit-es2015.js

Object.keys(obj)
.filter((key) => ['blacklisted', 'keys'].indexOf(key) < 0)
.reduce((newObj, key) => Object.assign(newObj, { [key]: obj[key] }), {})

Upvotes: 24

roNn23
roNn23

Reputation: 1652

I've got the same problem and solved it easily by using the following libs:

object.pick

https://www.npmjs.com/package/object.pick

pick({a: 'a', b: 'b', c: 'c'}, ['a', 'b'])
//=> {a: 'a', b: 'b'}

object.omit

https://www.npmjs.com/package/object.omit

omit({a: 'a', b: 'b', c: 'c'}, ['a', 'c'])
//=> { b: 'b' }

Upvotes: 1

Kamil Kiełczewski
Kamil Kiełczewski

Reputation: 92367

Dynamic solution

['color', 'height'].reduce((a,b) => (a[b]=elmo[b],a), {})

let subset= (obj,keys)=> keys.reduce((a,b)=> (a[b]=obj[b],a),{});


// TEST

let elmo = { 
  color: 'red',
  annoying: true,
  height: 'unknown',
  meta: { one: '1', two: '2'}
};

console.log( subset(elmo, ['color', 'height']) );

Upvotes: 7

Lauren
Lauren

Reputation: 2627

If you are using ES6 there is a very concise way to do this using destructuring. Destructuring allows you to easily add on to objects using a spread, but it also allows you to make subset objects in the same way.

const object = {
  a: 'a',
  b: 'b',
  c: 'c',
  d: 'd',
}

// Remove "c" and "d" fields from original object:
const {c, d, ...partialObject} = object;
const subset = {c, d};

console.log(partialObject) // => { a: 'a', b: 'b'}
console.log(subset) // => { c: 'c', d: 'd'};

Upvotes: 203

Igor
Igor

Reputation: 1311

Adding my 2 cents to Ivan Nosov answer:

In my case I needed many keys to be 'sliced' out of the object so it's becoming ugly very fast and not a very dynamic solution:

const object = { a: 5, b: 6, c: 7, d: 8, aa: 5, bb: 6, cc: 7, dd: 8, aaa: 5, bbb: 6, ccc: 7, ddd: 8, ab: 5, bc: 6, cd: 7, de: 8  };
const picked = (({ a, aa, aaa, ab, c, cc, ccc, cd }) => ({ a, aa, aaa, ab, c, cc, ccc, cd }))(object);

console.log(picked);

So here is a dynamic solution using eval:

const slice = (k, o) => eval(`(${k} => ${k})(o)`);


const object    = { a: 5, b: 6, c: 7, d: 8, aa: 5, bb: 6, cc: 7, dd: 8, aaa: 5, bbb: 6, ccc: 7, ddd: 8, ab: 5, bc: 6, cd: 7, de: 8  };
const sliceKeys = '({ a, aa, aaa, ab, c, cc, ccc, cd })';

console.log( slice(sliceKeys, object) );

Upvotes: -1

olivia
olivia

Reputation: 79

  1. convert arguments to array

  2. use Array.forEach() to pick the property

    Object.prototype.pick = function(...args) {
       var obj = {};
       args.forEach(k => obj[k] = this[k])
       return obj
    }
    var a = {0:"a",1:"b",2:"c"}
    var b = a.pick('1','2')  //output will be {1: "b", 2: "c"}
    

Upvotes: 1

Muhammet Enginar
Muhammet Enginar

Reputation: 516

Destructuring into dynamically named variables is impossible in JavaScript as discussed in this question.

To set keys dynamically, you can use reduce function without mutating object as follows:

const getSubset = (obj, ...keys) => keys.reduce((a, c) => ({ ...a, [c]: obj[c] }), {});

const elmo = { 
  color: 'red',
  annoying: true,
  height: 'unknown',
  meta: { one: '1', two: '2'}
}

const subset = getSubset(elmo, 'color', 'annoying')
console.log(subset)

Should note that you're creating a new object on every iteration though instead of updating a single clone. – mpen

below is a version using reduce with single clone (updating initial value passed in to reduce).

const getSubset = (obj, ...keys) => keys.reduce((acc, curr) => {
  acc[curr] = obj[curr]
  return acc
}, {})

const elmo = { 
  color: 'red',
  annoying: true,
  height: 'unknown',
  meta: { one: '1', two: '2'}
}

const subset = getSubset(elmo, 'annoying', 'height', 'meta')
console.log(subset)

Upvotes: 9

Ivan Nosov
Ivan Nosov

Reputation: 15945

Using Object Destructuring and Property Shorthand

const object = { a: 5, b: 6, c: 7  };
const picked = (({ a, c }) => ({ a, c }))(object);

console.log(picked); // { a: 5, c: 7 }


From Philipp Kewisch:

This is really just an anonymous function being called instantly. All of this can be found on the Destructuring Assignment page on MDN. Here is an expanded form

let unwrap = ({a, c}) => ({a, c});

let unwrap2 = function({a, c}) { return { a, c }; };

let picked = unwrap({ a: 5, b: 6, c: 7 });

let picked2 = unwrap2({a: 5, b: 6, c: 7})

console.log(picked)
console.log(picked2)

Upvotes: 1196

Josh from Qaribou
Josh from Qaribou

Reputation: 6898

While it's a bit more verbose, you can accomplish what everyone else was recommending underscore/lodash for 2 years ago, by using Array.prototype.reduce.

var subset = ['color', 'height'].reduce(function(o, k) { o[k] = elmo[k]; return o; }, {});

This approach solves it from the other side: rather than take an object and pass property names to it to extract, take an array of property names and reduce them into a new object.

While it's more verbose in the simplest case, a callback here is pretty handy, since you can easily meet some common requirements, e.g. change the 'color' property to 'colour' on the new object, flatten arrays, etc. -- any of the things you need to do when receiving an object from one service/library and building a new object needed somewhere else. While underscore/lodash are excellent, well-implemented libs, this is my preferred approach for less vendor-reliance, and a simpler, more consistent approach when my subset-building logic gets more complex.

edit: es7 version of the same:

const subset = ['color', 'height'].reduce((a, e) => (a[e] = elmo[e], a), {});

edit: A nice example for currying, too! Have a 'pick' function return another function.

const pick = (...props) => o => props.reduce((a, e) => ({ ...a, [e]: o[e] }), {});

The above is pretty close to the other method, except it lets you build a 'picker' on the fly. e.g.

pick('color', 'height')(elmo);

What's especially neat about this approach, is you can easily pass in the chosen 'picks' into anything that takes a function, e.g. Array#map:

[elmo, grover, bigBird].map(pick('color', 'height'));
// [
//   { color: 'red', height: 'short' },
//   { color: 'blue', height: 'medium' },
//   { color: 'yellow', height: 'tall' },
// ]

Upvotes: 112

Alexander Mills
Alexander Mills

Reputation: 100000

Good-old Array.prototype.reduce:

const selectable = {a: null, b: null};
const v = {a: true, b: 'yes', c: 4};

const r = Object.keys(selectable).reduce((a, b) => {
  return (a[b] = v[b]), a;
}, {});

console.log(r);

this answer uses the magical comma-operator, also: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_Operator

if you want to get really fancy, this is more compact:

const r = Object.keys(selectable).reduce((a, b) => (a[b] = v[b], a), {});

Putting it all together into a reusable function:

const getSelectable = function (selectable, original) {
  return Object.keys(selectable).reduce((a, b) => (a[b] = original[b], a), {})
};

const r = getSelectable(selectable, v);
console.log(r);

Upvotes: 0

Evert
Evert

Reputation: 99523

One more solution:

var subset = {
   color: elmo.color,
   height: elmo.height 
}

This looks far more readable to me than pretty much any answer so far, but maybe that's just me!

Upvotes: 62

MSi
MSi

Reputation: 107

This works for me in Chrome console. Any problem with this?

var { color, height } = elmo
var subelmo = { color, height }
console.log(subelmo) // {color: "red", height: "unknown"}

Upvotes: 2

user6445533
user6445533

Reputation:

Destructuring assignment with dynamic properties

This solution not only applies to your specific example but is more generally applicable:

const subset2 = (x, y) => ({[x]:a, [y]:b}) => ({[x]:a, [y]:b});

const subset3 = (x, y, z) => ({[x]:a, [y]:b, [z]:c}) => ({[x]:a, [y]:b, [z]:c});

// const subset4...etc.


const o = {a:1, b:2, c:3, d:4, e:5};


const pickBD = subset2("b", "d");
const pickACE = subset3("a", "c", "e");


console.log(
  pickBD(o), // {b:2, d:4}
  pickACE(o) // {a:1, c:3, e:5}
);

You can easily define subset4 etc. to take more properties into account.

Upvotes: -2

ManMohan Vyas
ManMohan Vyas

Reputation: 4062

Note: though the original question asked was for javascript, it can be done jQuery by below solution

you can extend jquery if you want here is the sample code for one slice:

jQuery.extend({
  sliceMe: function(obj, str) {
      var returnJsonObj = null;
    $.each( obj, function(name, value){
        alert("name: "+name+", value: "+value);
        if(name==str){
            returnJsonObj = JSON.stringify("{"+name+":"+value+"}");
        }

    });
      return returnJsonObj;
  }
});

var elmo = { 
  color: 'red',
  annoying: true,
  height: 'unknown',
  meta: { one: '1', two: '2'}
};


var temp = $.sliceMe(elmo,"color");
alert(JSON.stringify(temp));

here is the fiddle for same: http://jsfiddle.net/w633z/

Upvotes: -4

Ygg
Ygg

Reputation: 3870

I suggest taking a look at Lodash; it has a lot of great utility functions.

For example pick() would be exactly what you seek:

var subset = _.pick(elmo, ['color', 'height']);

fiddle

Upvotes: 262

Arthur Alvim
Arthur Alvim

Reputation: 1054

You can use Lodash also.

var subset = _.pick(elmo ,'color', 'height');

Complementing, let's say you have an array of "elmo"s :

elmos = [{ 
      color: 'red',
      annoying: true,
      height: 'unknown',
      meta: { one: '1', two: '2'}
    },{ 
      color: 'blue',
      annoying: true,
      height: 'known',
      meta: { one: '1', two: '2'}
    },{ 
      color: 'yellow',
      annoying: false,
      height: 'unknown',
      meta: { one: '1', two: '2'}
    }
];

If you want the same behavior, using lodash, you would just:

var subsets = _.map(elmos, function(elm) { return _.pick(elm, 'color', 'height'); });

Upvotes: 13

elclanrs
elclanrs

Reputation: 94101

How about:

function sliceObj(obj) {
  var o = {}
    , keys = [].slice.call(arguments, 1);
  for (var i=0; i<keys.length; i++) {
    if (keys[i] in obj) o[keys[i]] = obj[keys[i]];
  }
  return o;
}

var subset = sliceObj(elmo, 'color', 'height');

Upvotes: 1

Related Questions