"Removing" multiple properties from an object without mutation - javascript

I'm searching a for a way to create a function. in which I can pass an object and an array of properties (keys) I want gone. That function will return me a new object that doesn't have the keys I've specified.
function(keys: array, obj: object) {...}
Question is - how do I do that with multiple properties?
I've searched and only found this kind of solution:
const myObject = {
a: 1,
b: 2,
c: 3
};
const { a, ...noA } = myObject;
But it only works if I want to remove only ONE key. What if I want to remove multiple, using an array I just passed? How do I do that without mutating the original array or manually creating copies of it?

You could destructure the object by taking a computed property for unwanted properties.
const
without = (object, keys) => keys.reduce((o, k) => {
const { [k]: _ , ...p } = o;
return p;
}, object),
myObject = { a: 1, b: 2, c: 3 },
keys = ['a', 'b'],
result = without(myObject, keys);
console.log(result);

You can do it using reduce and Object.entries(). You can try this:
const myObject = {
a: 1,
b: 2,
c: 3,
d: 4
};
const removeProps = (object, keys) => {
return Object.entries(object).reduce((a, [key, value]) => (keys.indexOf(key) === -1 ? {...a, [key]: value}: a), {});
}
console.log(removeProps(myObject, ['b']));
console.log(removeProps(myObject, ['b', 'c']));
console.log('Original Object: ', myObject);
.as-console-wrapper{min-height: 100%!important; top: 0}

Above answers are great, I'm sharing my try:
var myObject = { a: 1, b: 2, c: 3, d: 4};
let remove=(obj, arr)=> {
let output=[];
for(const [key, value] of Object.entries(obj)){
if(!arr.includes(key)){
output.push([key,value]);
}
}
return Object.fromEntries(output);
}
console.log(remove(myObject, ['a']));
console.log(remove(myObject, ['a', 'c']));

Related

Edge cases for deep copy of an object in Javascript?

I'm trying to make a deep copy of an object without using lodash.
I'm unable to think of any case where this will fail. Can someone suggest me any edge case where this may fail?
let Obj = {
a: "Hello",
b: {
c: {
h: "World"
},
d: null
},
e: () => 'me',
f: new Date(),
g: [1, 2, 3]
}
let result = JSON.parse(JSON.stringify(Obj));
for (let key in Obj) {
if (!result.hasOwnProperty(key) || Obj[key] instanceof Date) {
result[key] = Obj[key];
}
}
console.log(result)

JavaScript - delete object properties in array of objects

Is there a better / shorter method to delete properties from objects in an array of objects than the below example. I can use vanilla JS or lodash.
Exmaple function:
function stripObjProps(arr) {
let newArr = _.clone(arr);
for (let i = 0; i < arr.length; i += 1) {
delete newArr[i].isBounded;
delete newArr[i].isDraggable;
delete newArr[i].isResizable;
delete newArr[i].maxH;
delete newArr[i].maxW;
delete newArr[i].minH;
delete newArr[i].minW;
delete newArr[i].resizeHandles;
delete newArr[i].moved;
delete newArr[i].static;
}
return newArr;
}
You can use omit from Lodash to exclude properties:
function stripObjProps(arr) {
return arr.map(item => _.omit(item, ['isBounded', 'isDraggable', 'isResizable', 'maxH', 'maxW', 'minH', 'minW', 'resizeHandles', 'moved', 'static']));
}
const newArray = stripObjProps(originalArray)
Additionally, you can use pick instead of omit. In case of pick you specify only the properties which you want to keep.
I can think of two ways
function stripObjProps(arr) {
let newArr = _.clone(arr);
for (let i = 0; i < newLay.length; i += 1) {
[
"isBounded",
"isDraggable",
"isResizable",
"maxH",
"maxW",
"minH",
"minW",
"resizeHandles",
"moved",
"static"
].forEach(k => delete newArr[i][k]);
}
}
or - assuming newLay is a typo
function stripObjProps(arr) {
return arr.map(item => {
let {
isBounded,
isDraggable,
isResizable,
maxH,
maxW,
minH,
minW,
resizeHandles,
moved,
static,
...ret
} = item;
return ret;
});
}
NOTE: no need for _.clone in this second example, since you aren't doing a deep clone, map returns a new array with a new object (...ret)
However, I don't use lodash, so there may be an even better way with that library
Another way to remove properties with filter and Object entries / fromEntries.
const data = [
{ a: 1, b: 2, c: 3, d: 4, e: 5 },
{ a: 1, b: 2, c: 3, d: 4, e: 5 },
{ a: 1, b: 2, c: 3, d: 4, e: 5 },
];
const excludeKeys = ['b', 'c', 'd'];
const exclude = (obj) =>
Object.fromEntries(
Object.entries(obj)
.filter(([key]) => !excludeKeys.includes(key)));
const result = data.map(exclude);
console.log(result);
.as-console-wrapper {max-height: 100% !important; top: 0}

Merge objects updating duplicate keys (case-insensitively)

I'm using a javascript module that has a configuration object. One is already set with defaults and the user can pass in values to overwrite these set values. I am using Object.assign to merge the two objects.
Here is an example:
const target = { a: 1, b: 2 }; // <-- default config
const source = { B: 4, c: 5 }; // <-- User input config
Object.assign(target, source);
console.log(target); //{a: 1, b: 2, B: 4, c: 5}
In this example if the user accidentally types in an uppercase 'B' instead of a lowercase one then the config object adds another value to itself when what I really want is the lowercase 'b' to be updated.
I know that's the intended behavior of Object.assign but trying to make this easier for the user and be case insensitive.
This version is a little different from the others. It normalizes only the keys found in the initial object, leaving the others intact. Something like this:
insensitiveAssign ({a: 1, b: 2}, {B: 4, c: 5, D: 6}) //=> {a: 1, b: 4, c: 5, D: 6}
// ^ ^ ^ ^
// unaltered --------------' | | |
// overwritten ------------------+ | |
// added ------------------------------+ |
// added (note: key not modified) -----------+
That may or may not be of use to you, but it's an interesting approach to the problem. It also does not modify either of your objects, creating an altered clone instead.
const insensitiveAssign = (target, source) => {
const keys = Object .keys (target) .reduce ((a, k) => ((a[k.toLowerCase()] = k), a), {})
return Object .entries (source) .reduce ((a, [k, v]) => {
const lowerK = k.toLowerCase()
const key = lowerK in keys ? keys[lowerK] : k
a[key] = v;
return a
}, Object.assign({}, target)) // start with a shallow copy
}
const target = {a: 1, b: 2};
const source = {B: 4, c: 5, D: 6};
console .log (
'result:',
insensitiveAssign (target, source),
)
console .log (
'target:',
target,
)
console .log (
'source:',
source
)
Update
A comment updated the question to ask how this might be applied to nested objects. In actuality, I would probably try to write that from scratch, but I don't have time now and a (only slightly tested) modification of this seems like it would work:
const insensitiveAssign = (target, source) => {
// if-block added
if (Object(target) !== target || (Object(source) !== source)) {
return source
}
const keys = Object .keys (target) .reduce ((a, k) => ((a[k.toLowerCase()] = k), a), {})
return Object .entries (source) .reduce ((a, [k, v]) => {
const lowerK = k.toLowerCase()
const key = lowerK in keys ? keys[lowerK] : k
a[key] = insensitiveAssign(target[key], v); // this line updated
return a
}, Object.assign({}, target))
}
const target = {a: 1, b: 2, x: {w: 'a', y: {z: 42}}};
const source = {B: 4, c: 5, D: 6, x: {V: 'c', Y: {z: 101}}};
console .log (
'result:',
insensitiveAssign (target, source),
)
console .log (
'target:',
target,
)
console .log (
'source:',
source
)
You'll have to lowercase the object keys first, like done here
const target = { a: 1, b: 2 }; // <-- default config
const source = { B: 4, c: 5 }; // <-- User input config
const lowerSource = Object.keys(source).reduce((c, k) => (c[k.toLowerCase()] = source[k], c), {});
Object.assign(target, lowerSource);
console.log(target);
You may simply remap source object lower-casing its keys with Object.keys() and Array.prototype.map(), then pass resulting key-value pairs as parameter to Object.assign():
const target = { a: 1, b: 2 },
source = { B: 4, c: 5 },
result = Object.assign(
target,
...Object
.keys(source)
.map(key =>
({[key.toLowerCase()]: source[key]}))
)
console.log(result)
You can try something like below code.
target = { a: 1, b: 2 }; // <-- default config
source = { B: 4, c: 5 }; // <-- User input config
source = JSON.parse(JSON.stringify(source).toLowerCase())
Object.assign(target, source);

How to merge two objects, overriding null values?

I'd like to merge two similar but not identical objects and override null values in one of them, if such exist. For example I'd have these two objects:
const obj1 = {
a: 1,
b: '',
c: [],
d: null
}
const obj2 = {
a: 2,
b: null,
d: 1
}
And the effect of merge should be:
const objMerged = {
a: 2,
b: '',
c: [],
d: 1
}
In other words, the most important source of data in the merged object is obj2 but it lacks some properties from obj1, so they need to be copied and also some of the obj2 values are null so they should be taken from obj1 as well.
EDIT
I tried:
_.extend({}, obj1, obj2)
and
Object.assign({}, obj1, obj2)
You could also mix and match with ES6 destructuring and lodash _.omitBy:
const obj1 = { a: 1, b: '', c: [], d: null }
const obj2 = { a: 2, b: null, d: 1 }
const result = {..._.omitBy(obj1, _.isNull), ..._.omitBy(obj2, _.isNull)}
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
You could also do it with ES6 only like this:
const obj1 = { a: 1, b: '', c: [], d: null }
const obj2 = { a: 2, b: null, d: 1 }
let omitNull = obj => {
Object.keys(obj).filter(k => obj[k] === null).forEach(k => delete(obj[k]))
return obj
}
const result = { ...omitNull(obj1), ...omitNull(obj2) }
console.log(result)
To add to this list of good answers, here's a recursive solution that will work with nested structures.
This example will merge the common properties of the dst object to the src object in all levels of nesting, leaving any properties that are not common intact.
const merge = (dst, src) => {
Object.keys(src).forEach((key) => {
if (!dst[key]) {
dst[key] = src[key];
} else if (typeof src[key] === 'object' && src[key] !== null && typeof dst[key] === 'object' && dst[key] !== null) {
merge(dst[key], src[key]);
}
});
},
/* Usage: */
src = {
prop1: '1',
prop2: {
val: 2,
}
},
dst = {
prop1: null,
prop2: {
val: null,
},
prop3: null,
};
merge(dst, src);
console.log(dst);
You can use _.mergeWith(), and in the merge callback only take the 2nd value if it's not null:
const obj1 = { a: 1, b: '', c: [], d: null }
const obj2 = { a: 2, b: null, d: 1 }
const result = _.mergeWith({}, obj1, obj2, (o, s) => _.isNull(s) ? o : s)
console.log(result)
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.11/lodash.min.js"></script>
Here is a pure JS based solution:
Iterate through the first object to replace values from second object, then add the additional values from the second object.
const obj1 = {
a: 1,
b: '',
c: [],
d: null
}
const obj2 = {
a: 2,
b: null,
d: 1
}
function mergeObjs(obj1, obj2){
const merged = {}
keys1 = Object.keys(obj1);
keys1.forEach(k1 => {
merged[k1] = obj2[k1] || obj1[k1]; // replace values from 2nd object, if any
})
Object.keys(obj2).forEach(k2 => {
if (!keys1.includes(k2)) merged[k2] = obj[k2]; // add additional properties from second object, if any
})
return merged
}
console.log(mergeObjs(obj1, obj2))
Using Lodash by create() and omitBy()
const obj1 = {"a":1,"b":"","c":[],"d":null}
const obj2 = {"a":2,"b":null,"d":1}
const objMerged = _.create(
_.omitBy(obj1, _.isNull),
_.omitBy(obj2, _.isNull)
)
console.log(objMerged)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
If you're interested in only the first level of the two objects you could do something like this:
const obj1 = {
a: 1,
b: '',
c: [],
d: null
}
const obj2 = {
a: 2,
b: null,
d: 1
}
const merged = Object.keys(obj1).concat(Object.keys(obj2)) // create an array that contains the keys of the two objects.
.filter((k, i, arr) => arr.indexOf(k) === i) // remove duplicate keys
.reduce((a, c) => {
a[c] = obj1[c] !== null ? obj1[c] : obj2[c];
return a;
}, {});
console.log(merged);
This example only check for null values, you should probably extend it to check for others like undefined, empty strings, etc.
You did it the good way using Object.assign, just remove what you don't want right before
Object.keys(obj1).forEach( k => {
if ( obj1[k] //write the condition you want
delete obj1[k]
});
var objMerged = {};
for (var kobj1 in obj1) {
for (var kobj2 in obj2) {
if (obj1[kobj1] == null && obj2[kobj1] != null)
objMerged[kobj1] = obj2[kobj1];
else if (obj2[kobj2] == null && obj1[kobj2] != null)
objMerged[kobj2] = obj1[kobj2];
}
}
//Print objMerged to display

Copy all elements from one object except those with that share their key with an element in another object

I have two objects:
obj1 = {
a: 1,
b: 2,
c: 3,
d: 4
}
and
obj2 = {
a: "foo",
c: "bar"
}
From those I want to create a third object:
obj3 = {
b: 2,
d: 4
}
The third object should contain all elements from the first object, except those that share their key with an element in the second object.
I know ways to achieve this if the excluded keys are hard coded:
const { a, c, ...obj3 } = obj1;
or
const obj3 = Object.assign({}, obj1);
[ "a", "c" ].forEach(key => delete obj3[key]);
Is there a way to do this if the excluded keys are not hard coded but instead also keys of another object, like I described above?
The reason I need to do this is because in my react app I want to spread all props given to a container component to it's child component, except the props defined in the container component's PropTypes (the props used by the container component that shouldn't be passed on).
Your last example is a good start. You can combine it with Object.keys to achieve what you want:
let obj1 = {
a: 1,
b: 2,
c: 3,
d: 4
}
let obj2 = {
a: "foo",
c: "bar"
}
const obj3 = Object.assign({}, obj1);
Object.keys(obj2).forEach(key => delete obj3[key]);
console.log(obj3);
Object.keys applied to an object returns a string array containing the keys of the object.
Iterate through the keys of the first object obj1 and check whether the key is present in obj2, if not present in both obj1 and obj2 add it to the third obj3.
let obj1 = {
a: 1,
b: 2,
c: 3,
d: 4
}
let obj2 = {
a: "foo",
c: "bar"
}
let obj3 = {};
Object.keys(obj1).forEach((key)=>{ if(!(key in obj2)){
obj3[key] = obj1[key];
}
});
You can use Object.entries() with Array.reduce(), and object spread to construct a new object from all the keys of obj1 that don't exist on obj2:
const objectDiff = (o1, o2) =>
Object.entries(o1) // get an array of key/value pairs
.reduce((r, [k, v]) => ({
...r, // add the previous state of the object
...k in o2 || { [k]: v } // if key is not in the 2nd object add it
}), {});
const obj1 = {
a: 1,
b: 2,
c: 3,
d: 4
}
const obj2 = {
a: "foo",
c: "bar"
}
const result = objectDiff(obj1, obj2);
console.log(result);
Approach 1: using Object.keys:
const obj4 = Object.assign({}, obj1);
Object.keys(obj2).forEach(key => delete obj4[key]);
Approach 2: Another cleaner way, using for in loop with hasOwnProperty:
function filterKeys(obj1, obj2) {
const returnObj = {};
for (const prop in obj1) {
if (obj1.hasOwnProperty(prop) && !obj2.hasOwnProperty(prop)) {
console.log(`obj1.${prop} = ${obj1[prop]}`);
returnObj[prop] = obj1[prop];
}
}
return returnObj;
}
You can also use Object.keys, Array.prototype.filter and Array.prototype.reduce to achieve the desired output:
const obj1 = {a: 1,b: 2,c: 3,d: 4}
const obj2 = {a: "foo",c: "bar"}
const obj3 = Object.keys(obj1).filter(k => !obj2.hasOwnProperty(k)).reduce((a,e) => (a[e] = obj1[e], a),{});
console.log(obj3);

Categories