25

I have an object. I would like to modify the object (not clone it) by removing all properties except for certain specific properties. For instance, if I started with this object:

var myObj={
    p1:123,
    p2:321,
    p3:{p3_1:1231,p3_2:342},
    p4:'23423',
    //....
    p99:{p99_1:'sadf',p99_2:234},
    p100:3434
}

and only want properties p1, p2, and p100, how can I obtain this object:

var myObj={
    p1:123,
    p2:321,
    p100:3434
}

I understand how I could do this with brute force, but would like a more elegant solution.

user1032531
  • 24,028
  • 57
  • 174
  • 325
  • I don't think this is possible without using brute force(or cloning the object) – tewathia Mar 05 '14 at 16:06
  • What is "brute force"? – Andy Mar 05 '14 at 16:08
  • @Andy. I guess I am looking just for the most elegant solution. – user1032531 Mar 05 '14 at 16:11
  • 1
    I guess you have a good time User, many things to read ;) – leaf Mar 05 '14 at 16:34
  • @procrastinator Yes I do! I was hoping one would unanimously raise to the top. – user1032531 Mar 05 '14 at 16:44
  • 1
    This guy gave one of the best answers first (second code block) : http://stackoverflow.com/a/22202827/1636522. However, as mentioned by [George Jempty](http://stackoverflow.com/questions/22202766/keeping-only-certain-properties-in-a-javascript-object/22202827#comment33708475_22202892), old browsers won't like `indexOf`. A workaround can be found here though : http://mdn.beonex.com/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/indexOf.html. – leaf Mar 05 '14 at 17:08
  • Possible duplicate of https://stackoverflow.com/questions/25553910/one-liner-to-take-some-properties-from-object-in-es-6 (the only difference is that this question allows in-place modification, however, most answers re-initialize the object) – user202729 Jul 31 '18 at 15:22
  • This approach `({id, title}) => ({id, title})` worked for me: https://stackoverflow.com/a/25554551/470749 – Ryan May 15 '21 at 22:52
  • 1
    @Ryan Nice answer. `let result = (({ p1, p2, p100 }) => ({ p1, p2, p100 }))(myObj);` worked perfect. Not sure whether it is to late to switch my selected answer but will try if you wish to post this as an answer. Thanks – user1032531 May 18 '21 at 12:10

12 Answers12

14

Just re-initialise the object:

myObj = {
    p1:   myObj.p1,
    p2:   myObj.p2,
    p100: myObj.p100
};

Another way is to delete certain properties, which is less effective:

var prop = ['p1', 'p2', 'p100'];
for (var k in myObj) {
    if (prop.indexOf(k) < 0) {
        delete myObj[k];
    }
}
VisioN
  • 132,029
  • 27
  • 254
  • 262
  • 1
    He doesn't want to clone it – Juan Mendes Mar 05 '14 at 16:02
  • 3
    @JuanMendes It won't clone it. – VisioN Mar 05 '14 at 16:02
  • @JuanMendes it doesn't really clone it. It overrides it so in the end you still only have the one object. – Joseph Marikle Mar 05 '14 at 16:02
  • 3
    But you still don't have the object you started with – Juan Mendes Mar 05 '14 at 16:03
  • @JuanMendes But what's the point of keeping the first object? Do you need to use exactly the same memory slot? – VisioN Mar 05 '14 at 16:06
  • 4
    The second code block is effective, unlike the first one. Indeed, what if another variable refers to `myObj`? – leaf Mar 05 '14 at 16:08
  • 3
    @VisioN What about any other references to the first object ? – Igor Malyk Mar 05 '14 at 16:08
  • @procrastinator Can't agree about the efficiency but regarding the referred objects, it makes sense. – VisioN Mar 05 '14 at 16:20
  • 1
    @VisioN Sorry, I mixed up words, thanks to my bad english :/ I meant, it doesn't produce the expected effect, that is to say, modify the original object. – leaf Mar 05 '14 at 16:29
  • I suppose reinitializing the object is best if one only wants to keep a couple of properties. If most are to remain, then delete those one doesn't want. I suppose it might be better to loop over the properties to remain, and just delete those, no? – user1032531 Mar 05 '14 at 22:26
  • @user1032531 Reinitialising the object is always better performance-wise and if you do not need to keep references to the original object, as was discussed above. However, if you need to keep most of the properties, then partial deletion is the way to go, just to keep code shorter. – VisioN Mar 05 '14 at 22:35
  • 3
    The amount of properties is not the problem @user1032531, focus on this sentence : "if you do not need to keep references to the original object". Give me a minute, I'll post an answer to explain this to you. – leaf Mar 05 '14 at 23:06
14

This was the first hit when googling 'js keep only certain keys' so might be worth an update.

The most 'elegant' solution might just be using underscore.js

_.pick(myObj, 'p1', 'p2', 'p100')
Alf
  • 752
  • 6
  • 17
5

You could use delete:

for (var k in myObj) {
    if (k !== 'p1' && k !== 'p2' && k !== 'p100') {
        delete myObj[k];
    }
}

An alternative to indexOf:

var take = /^p(1|2|100)$/;
for (var k in myObj) {
    if (!take.test(k)) {
        delete myObj[k];
    }
}

Shorter:

var take = /^p(1|2|100)$/;
for (var k in myObj) {
    take.test(k) || delete myObj[k];
}

Array to RegExp:

var take = [1, 2, 100];
take = new RegExp('^p(' + take.join('|') + ')$'); // /^p(1|2|100)$/
take.test('p1'); // true
take.test('p3'); // false

Useful in a function:

function take(o, ids) {
    var take = new RegExp('^p(' + ids.join('|') + ')$');
    for (var k in o) take.test(k) || delete o[k];
    return o;
}

Usage:

take(myObj, [1, 2, 100]); // { p1: 123, p2: 321, p100: 3434 }

If you don't like regular expressions:

function take(o, keys) {
    for (var k in o) contains(keys, k) || delete o[k];
    return o;
}

function contains(array, value) {
    var i = -1, l = array.length;
    while (++i < l) if (array[i] === value) return true;
    return false;
}

function prefix(array, prefix) {
    var i = -1, l = array.length, output = [];
    while (++i < l) output.push(prefix + array[i]);
    return output;
}

Usage:

take(myObj, ['p1', 'p2', 'p100']);
// with a middleman :
var idsToTake = [1, 2, 100];
take(myObj, prefix(idsToTake, 'p'));
leaf
  • 14,210
  • 8
  • 49
  • 79
5

Lodash has a function called pick which does what you're describing. I know that including a library isn't ideal, but you can also cherry-pick functions when using bundles, etc.

var myObj={
    p1:123,
    p2:321,
    p3:{p3_1:1231,p3_2:342},
    p4:'23423',
    //....
    p99:{p99_1:'sadf',p99_2:234},
    p100:3434
}

var newObj = _.pick(myObj, 'p1', 'p2', 'p100')
Grant
  • 1,102
  • 1
  • 13
  • 20
3
var myObj = {a: 1, b: 2, c:3};

function keepProps(obj, keep) {
    for (var prop in obj) {
        if (keep.indexOf( prop ) == -1) {
            delete obj[prop];
        }             
    }
}

keepProps(myObj, ['a', 'b']);
console.log(myObj);

http://jsfiddle.net/mendesjuan/d8Sp3/2/

Juan Mendes
  • 80,964
  • 26
  • 138
  • 189
  • Looks like brute force to me, and he doesn't want that – tewathia Mar 05 '14 at 16:05
  • 3
    @tewathia Please elaborate "brute force". This is the most dynamic way to do it based on OP's question. – Johan Mar 05 '14 at 16:07
  • Well, you are going through All the properties of the object(I agree this really is the best way of getting the desired object without cloning it), and I believe that's what he meant by "brute force" – tewathia Mar 05 '14 at 16:08
  • What's `var keep a` by the way? – tewathia Mar 05 '14 at 16:10
  • 2
    Why do you use 2 nested loops and `indexOf`? – VisioN Mar 05 '14 at 16:11
  • There will be an issue if the browser being used doesn't support indexOf and even if it does, numerous calls to indexOf inside a loop are inefficient -- this is why I used an object instead of an array for the whitelist in my answer – Dexygen Mar 05 '14 at 16:50
  • 1
    @GeorgeJempty You're very persnickety ;) [My answer](http://stackoverflow.com/a/22202905/1636522) is cross browser compatible by the way :D – leaf Mar 05 '14 at 17:01
  • @GeorgeJempty That's only true if your property count and properties to keep are really high, which is not the case here. For this case, the map lookup is actually more expensive since arrays are optimized nowadays. You do have a valid point but I don't think it applies here. A polyfill for `indexOf` is available https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf#Polyfill – Juan Mendes Mar 06 '14 at 23:19
2

An object stored in a variable named o :

var o = { a: 1, b: 2 };

A new reference to this object :

var p = o;

o and p both refer to the same object :

o // { a: 1, b: 2 }
p // { a: 1, b: 2 }
o === p // true

Let's update the object through o :

delete o.b;
o // { a: 1 }
p // { a: 1 }

Let's update the object through p :

p.b = 2;
o // { a: 1, b: 2 }
p // { a: 1, b: 2 }

As you can see, o and p are in sync.


Let's "reinitialize" o :

o = { a: o.a };

o and p now refer to different objects :

o // { a: 1 }
p // { a: 1, b: 2 }
o === p // false

Let's update the object stored in o :

o.c = 3;
o // { a: 1, c: 3 }
p // { a: 1, b: 2 }

Let's update the object stored in p :

delete p.a;
o // { a: 1, c: 3 }
p // { b: 2 }

As you can see, o and p are not in sync anymore.


The question is : do you want to keep both variables (o and p) synchronized? If so, the second code block of VisioN's answer is the right one, otherwise, choose the first code block.

Community
  • 1
  • 1
leaf
  • 14,210
  • 8
  • 49
  • 79
2

You can code your own implementation of _.pick, and use it according to your needs.

Having this snippet of code as the base for the following cases:

const myObj={
    p1:123,
    p2:321,
    p3:{p3_1:1231,p3_2:342},
    p4:'23423',
    //....
    p99:{p99_1:'sadf',p99_2:234},
    p100:3434
}
let properties= ['p1','p2', 'p3', 'p100'];

case 1:

You want a shallow copy (with references to vector values)

const myNewObj = properties.reduce((newObj,property)=>{newObj[property] = myObj[property]; return newObj}, {})
// if we modify the original vector value of 'p3' in `myObj` we will modify the copy as well:

myObj.p3.p3_1 = 99999999999;

console.log(myNewObj); // { p1: 123,​​​​​​​​​​  p2: 321,​​​​​​​​​​  p3: { p3_1: 99999999999, p3_2: 42 },​​​​​​​​​​  p100: 3434 }​​​​​

case 2:

You want a deep copy (losing references to vector values)

You can just use JSON utilities to that matter.

const myNewObj2 = properties.reduce((newObj,property)=>{newObj[property] = JSON.parse(JSON.stringify(myObj[property])); return newObj},{})

// no matter how hard you modify the original object, you will create a new independent object
myObj.p3.p3_1 = 99999999999;

console.log(myNewObj2) // { p1: 123, p2: 321, p3: { p3_1: 1231, p3_2: 342 }, p100: 3434 }​​​​​

Reusing case 2 with a function

You could implement a reducer to use it in different scenarios, like this one:

function reduceSelectedProps(origin, properties){
  return (newObj,property)=>{
    newObj[property] = JSON.parse(JSON.stringify(origin[property]));
     return newObj
  }
}

So you could have a more elegant reuse of it:

const myNewObj3 = properties.reduce(reduceSelectedProps(myObj, properties),{});
// no matter how hard you modify the original object, you will create a new independent object
myObj.p3.p3_1 = 99999999999;

console.log(myNewObj3) // { p1: 123, p2: 321, p3: { p3_1: 1231, p3_2: 342 }, p100: 3434 }​​​​​

disclaimers:

This is only an example that does not handle Date, Set, Map or function values inside the properties. To deal with all these cases (and many others), it needs a really complex function with checks on the prototypes and all that stuff. At this point, consider reusing the work of other developers using any library that could do it. Lodash?

Soldeplata Saketos
  • 2,215
  • 19
  • 33
0

You could create a view on your first object, some kind of proxy that would only keep the desired properties on sight.
For instance the following function will create a view that allows to both read and write the underlying object, keeping only the choosen properties.
You can make it readonly very easily, by just removing the setter.
You might also want to seal the proxy object, so that no later modification can me made to it.

function createView(obj, propList) {
    var proxy = {};     
    for (var propIndex in propList) {    
         var prop=propList[propIndex];
         Object.defineProperty(proxy, prop, 
                {  enumerable : true , 
                   get : getter.bind(obj,prop), 
                   set : setter.bind(obj,prop)   } );

    }    
  return proxy;
}

function getter(prop) { return this[prop] ; }

function setter(prop, value) { return this[prop] = value ; }

An example of use would be :

var myObj={
    p1:123,
    p2:321,
    p3:{p3_1:1231,p3_2:342},
    p4:'23423',
    p99:{p99_1:'sadf',p99_2:234},
    p100:3434
};

var objView = createView(myObj, ['p1', 'p2', 'p100']);

Here, objView 'reflects' the desired properties of myObj. You can look at the small jsbin i made here :

http://jsbin.com/munudewa/1/edit?js,console

results :

"on objView, p1:123 p2:321 p100:3434 and p4 (not in view) is : undefined"
"modifiying, on the view,  p1 to 1000 and p2 to hello "
"on objView, p1:1000 p2:hello p100:3434 and p4 (not in view) is : undefined"
"modifiying, on the viewed object,  p1 to 200 and p2 to bye "
"on objView, p1:200 p2:bye p100:3434 and p4 (not in view) is : undefined"

notice that :
1) you can overwrite an object by its view, only keeping desired properties.
2) you can save in a hidden property / in a closure, the original object, so you can later change the properties you expose.

GameAlchemist
  • 17,761
  • 6
  • 28
  • 54
0

I suppose you could add a new method to the prototype:

if (!('keepOnlyTheseProps' in Object.prototype)) {
  Object.prototype.keepOnlyTheseProps = function (arr) {
    var keys = Object.keys(this);
    for (var i = 0, l = keys.length; i < l; i++) {
      if (arr.indexOf(keys[i]) < 0) delete this[keys[i]];
    }
  }
}

myObj.keepOnlyTheseProps(['p1', 'p2', 'p100']);

Fiddle.

Andy
  • 39,764
  • 8
  • 53
  • 80
0

Pass a map of whitelisted keys into an IIFE (immediately invoked function expression); not just elegant but also flexible IMO (especially if moved off into a function not unlike in Juan Mendes' answer)

var myObj={
    p1:123,
    p2:321,
    p3:{p3_1:1231,p3_2:342},
    p4:'23423',
    //....
    p99:{p99_1:'sadf',p99_2:234},
    p100:3434
}

var myObj = (function(origObj, whiteListMap) {
    for (var prop in origObj) {
        if (!whiteListMap[prop]) {
            delete origObj[prop];
        }
    }
    return myObj;
})(myObj, {'p1': 1, 'p2': 1, 'p100': 1});

console.log(JSON.stringify(myObj)); //{"p1":123,"p2":321,"p100":3434}
Dexygen
  • 11,681
  • 11
  • 73
  • 144
  • If the whitelisted property's value is null, 0, '', or undefined it won't be in the built object. – GameAlchemist Mar 05 '14 at 16:54
  • @GameAlchemist, that is why I always set them to 1. Another approach is to pass in a whitelist array but then create an object out of it; this is a bit easier on the user. I disagree with using indexOf on the array though; even natively it requires looping instead of a simple hash lookup – Dexygen Mar 05 '14 at 17:00
  • you can use 'in' to avoid having to set to one : if (prop in whiteListMap). – GameAlchemist Mar 05 '14 at 17:12
0

I Made this short solution for case where I have an array with objects.

so consider the array below?

arr=[{"a1":"A1","b1":"B1"},{"a1":"Z1","b1":"X1"}];
console.log(arr);

I want to keep only "b1" properties of all objects.

You can use map() and delete for that as follows:

arr=[{"a1":"A1","b1":"B1"},{"a1":"Z1","b1":"X1"}];
arr=arr.map(function(d){
        delete d["a1"];
        return d;

    });
console.log(arr);

result is an array with objects but only "b1" properties.

0

You could use this approach:

let result = (({ p1, p2, p100 }) => ({ p1, p2, p100 }))(myObj);

which I learned at https://stackoverflow.com/a/25554551/470749.

Ryan
  • 17,332
  • 24
  • 141
  • 270