Related
I'm trying to work around the fact that Datocms doesn't support a where filter in their GraphQL schema. Since there isn't that much data, I figured I could query all of it, and do the find on my end, but ... I'm not succeeding, at least not using "modern" methods.
What I get back when I query all of the data looks like this:
"foo": {
"data": {
"allGiveawayLandingPages": [
{
"lpSection": [
{},
{},
{},
{},
{},
{},
{},
{
"id": "34525949",
"products": [
{
"__typename": "PurchaseCardRecord",
"discountAmount": 50,
"discountAmountPct": null,
"discountEndDate": "2022-11-01T23:00:00+00:00",
"id": "44144096"
},
{
"__typename": "PurchaseCardRecord",
"discountAmount": null,
"discountAmountPct": null,
"discountEndDate": null,
"id": "44144097"
}
]
}
]
}
]
}
}
I need to find the object down in the "products" array by "id". This general question has been asked and answered lots of times, but the only answer I can get to work is from way back in 2013, and it seems to me there aught to be a more modern way to do it.
I'm doing this inside of a try/catch block, which I mention because it seems to be making this hard to debug (I'll come back to this):
export default async function createPaymentIntentHandler(req, res) {
const body = JSON.parse(req.body);
const {
productId,
productType
} = body;
let data;
if ('POST' === req.method) {
try {
switch (productType) {
case 'SeminarRecord':
data = await request({ query: singleSeminarQuery(productId) });
productObjName = 'seminar';
break;
default:
data = await request({ query: singleProductQuery(productId) });
productObjName = 'product';
}
/**
* Here's where I want to do my query / filtering
*/
// ... do more stuff and create Stripe paymentIntent
res.status(200).send({clientSecret: paymentIntent.client_secret})
} catch (error) {
logger.error({error}, 'Create Payment Intent error');
return res.status(400).end(`Create Payment Intent error: ${error.message}`);
}
} else {
res.status(405).end('Method not allowed');
}
}
My first, naive attempt was
const foo = await request({ query: ALL_PURCHASE_CARDS_QUERY });
const card = foo.data.allGiveawayLandingPages.find((page) => {
return page.lpSection.find((section) => {
return section?.products.find((record) => record.id === parentId)
})
});
logger.debug({card}, 'Got card');
In the abstract, aside from the fact that the above is fairly brittle because it relies on the schema not changing, I'd expect some similar sort of ES6 construction to work. This particular one, however, throws, but not in a particularly useful way:
[08:09:18.690] ERROR: Create Payment Intent error
env: "development"
error: {}
That's what I meant by it being hard to debug — I don't know why the error object is empty. But, in any case, that's when I started searching StackOverflow. The first answer which looked promising was this one, which I implemented as
...
const {
productId,
productType,
parentId
} = body;
...
function findCard(parent, id) {
logger.debug({parent}, 'searching in parent')
for (const item of parent) {
if ('PurchaseCardRecord' === item.__typename && item.id === id) return item;
if (item.children?.length) {
const innerResult = findCard(item.children, id);
if (innerResult) return innerResult;
}
}
}
if ('POST' === req.method) {
try {
...
const foo = await request({ query: ALL_PURCHASE_CARDS_QUERY });
const card = findCard(foo, parentId);
logger.debug({card}, 'Got card');
This similarly throws unhelpfully, but my guess is it doesn't work because in the structure, not all children are iterables. Then I found this answer, which uses reduce instead of my original attempt at find, so I took a pass at it:
const card = foo.data.allGiveawayLandingPages.reduce((item) => {
item?.lpSection.reduce((section) => {
section?.products.reduce((record) => {
if ('PurchaseCardRecord' === record.__typename && record.id === parentId) return record;
})
})
})
This is actually the closest I've gotten using ES6 functionality. It doesn't throw an error; however, it's also not returning the matching child object, it's returning the first parent object that contains the match (i.e., it's returning the whole "lpSection" object). Also, it has the same brittleness problem of requiring knowledge of the schema. I'm relatively certain something like this is the right way to go, but I'm just not understanding his original construction:
arr.reduce((a, item) => {
if (a) return a;
if (item.id === id) return item;
I've tried to understand the MDN documentation for Array.reduce, but, I don't know, I must be undercaffeinated or something. The syntax is described as
reduce((previousValue, currentValue) => { /* … */ } )
and then several variations on the theme. I thought it would return all the way up the stack in my construction, but it doesn't. I also tried
const card = foo.data.allGiveawayLandingPages.reduce((accumulator, item) => {
return item?.lpSection.reduce((section) => {
return section?.products.reduce((record) => {
if ('PurchaseCardRecord' === record.__typename && record.id === parentId) return record;
})
})
})
but the result was the same. Finally, not understanding what I'm doing, I went back to an older answer that doesn't use the ES6 methods but relies on recursing the object.
...
function filterCards(object) {
if (object.hasOwnProperty('__typename') && object.hasOwnProperty('id') && ('PurchaseCardRecord' === object.__typename && parentId === object.id)) return object;
for (let i=0; i<Object.keys(object).length; i++) {
if (typeof object[Object.keys(object)[i]] == 'object') {
const o = filterCards(object[Object.keys(object)[i]]);
if (o != null) return o;
}
}
return null;
}
if ('POST' === req.method) {
try {
...
const foo = await request({ query: ALL_PURCHASE_CARDS_QUERY });
const card = filterCards(foo);
logger.debug({card}, 'Got card');
This actually works, but ISTM there should be a more elegant way to solve the problem with modern Javascript. I'm thinking it's some combination of .find, .some, and .reduce. Or maybe just for ... in.
I'll keep poking at this, but if anyone has an elegant/modern answer, I'd appreciate it!
What is a better way of doing this. I'am assigning either of two property values (from two different objects), depending on their existence, to a third data-structure.
In case the args object's value is nullish a non nullish value gets accessed from the default object and assigned to the final structure.
return {
first: {
visible: args.first?.visible ?? defaulttest.first?.visible,
emoji: args.first?.emoji ?? defaulttest.first?.emoji,
style: args.first?.style ?? defaulttest.first?.style,
},
back: {
visible: args.back?.visible ?? defaulttest.back?.visible,
emoji: args.back?.emoji ?? defaulttest.back?.emoji,
style: args.back?.style ?? defaulttest.back?.style,
},
page: {
visible: args.page?.visible ?? defaulttest.page?.visible,
emoji: args.page?.emoji ?? defaulttest.page?.emoji,
style: args.page?.style ?? defaulttest.page?.style,
},
forward: {
visible: args.forward?.visible ?? defaulttest.forward?.visible,
emoji: args.forward?.emoji ?? defaulttest.forward?.emoji,
style: args.forward?.style ?? defaulttest.forward?.style,
},
last: {
visible: args.last?.visible ?? defaulttest.last?.visible,
emoji: args.last?.emoji ?? defaulttest.last?.emoji,
style: args.last?.style ?? defaulttest.last?.style,
},
Mdelete: {
visible: args.Mdelete?.visible ?? defaulttest.Mdelete?.visible,
emoji: args.Mdelete?.emoji ?? defaulttest.Mdelete?.emoji,
style: args.Mdelete?.style ?? defaulttest.Mdelete?.style,
},
removeBtn: {
visible: args.removeBtn?.visible ?? defaulttest.removeBtn?.visible,
emoji: args.removeBtn?.emoji ?? defaulttest.removeBtn?.emoji,
style: args.removeBtn?.style ?? defaulttest.removeBtn?.style,
},
};
From my above comments ...
1/2 ... The OP actually is not really comparing. For a certain set of properties the OP looks up each property at a target object, and only in case it features a nullish value there will be an assignment from a source object's counterpart to the missing property. Thus an approach I would choose was ...
2/2 ... implementing a generic function which merges two objects in a way that a source property can only be written/assigned in case the target structure does not already provide a non nullish value. This function then has to be invoked twice once for args and defaulttest and a second time for the to be returned entirely empty data structure and args.
The above statement was a bit ambitious for there are at least 2 strategies of how one could achieve such kind of mergers.
Thus the below provided example code implements two approaches
one, called refit, which follows a pushing/patching agenda due to forcing the assignement of every non nullish property in a source-object to its non nullish counterpart of a target-object.
a 2nd one, called revive, which resembles a pulling approach for it just reassigns the nullish target-object properties with their non nullish source-object counterparts.
The difference in the results they produce for one and the same preset is going to be demonstrated herby ...
// "refit" ... a pushing/patching approach.
// - force the assignement of every non nullish property in source
// to its non nullish counterpart in target ... hence a *refit*.
function refitNullishValuesRecursively(target, source) {
if (
// are both values array-types?
Array.isArray(source) &&
Array.isArray(target)
) {
source
// for patching always iterate the source items ...
.forEach((sourceItem, idx) => {
// ... and look whether a target counterpart exists.
if (target[idx] == null) {
// either assign an existing structured clone ...
if (sourceItem != null) {
target[idx] = cloneDataStructure(sourceItem);
}
} else {
// ... or proceed recursively.
refitNullishValuesRecursively(target[idx], sourceItem);
}
});
} else if (
// are both values object-types?
source && target &&
'object' === typeof source &&
'object' === typeof target
) {
Object
// for patching ...
.entries(source)
// ... always iterate the source entries (key value pairs) ...
.forEach(([key, sourceValue], idx) => {
// ... and look whether a target counterpart exists.
if (target[key] == null) {
// either assign an existing structured clone ...
if (sourceValue != null) {
target[key] = cloneDataStructure(sourceValue);
}
} else {
// ... or proceed recursively.
refitNullishValuesRecursively(target[key], sourceValue);
}
});
}
return target;
}
// "revive" ... a pulling approach.
// - just reassign the nullish target properties with their
// non nullish source counterparts ... hence a *revive*.
function reviveNullishValuesRecursively(target, source) {
if (
// are both values array-types?
Array.isArray(target) &&
Array.isArray(source)
) {
target
// for fixing always iterate the target items.
.forEach((targetItem, idx) => {
if (targetItem == null) {
// either assign an existing structured clone ...
target[idx] = cloneDataStructure(source[idx]) ?? targetItem;
} else {
// ... or proceed recursively.
reviveNullishValuesRecursively(targetItem, source[idx]);
}
});
} else if (
// are both values object-types?
target && source &&
'object' === typeof target &&
'object' === typeof source
) {
Object
// for fixing ...
.entries(target)
// ... always iterate the target entries (key value pairs).
.forEach(([key, targetValue], idx) => {
if (targetValue == null) {
// either assign an existing structured clone ...
target[key] = cloneDataStructure(source[key]) ?? targetValue;
} else {
// ... or proceed recursively.
reviveNullishValuesRecursively(targetValue, source[key]);
}
});
}
return target;
}
const cloneDataStructure =
('function' === typeof structuredClone)
&& structuredClone
|| (value => JSON.parse(JSON.stringify(value)));
const targetBlueprint = {
x: { xFoo: 'foo', xBar: 'bar', xBaz: { xBiz: null } },
y: { yFoo: 'foo', yBar: null },
};
const patch = {
x: { xFoo: null, xBar: null, xBaz: { xBiz: 'biz' } },
y: { yFoo: null, yBar: 'bar', yBaz: { yBiz: 'biz' } },
};
let target = cloneDataStructure(targetBlueprint);
console.log('"refit" ... a pushing/patching approach.');
console.log('before refit ...', { target, patch });
refitNullishValuesRecursively(target, patch);
console.log('after refit ...', { target, patch });
target = cloneDataStructure(targetBlueprint);
console.log('"revive" ... a pulling approach.');
console.log('before revive ...', { target, patch });
reviveNullishValuesRecursively(target, patch);
console.log('after revive ...', { target, patch });
.as-console-wrapper { min-height: 100%!important; top: 0; }
As for the OP's example which targets the creation of kind of a config-object, one could fully patch/refit a clone of the temporary or current args-config, whereas within the last step one has control over the config-object's final structure by providing the most basic empty config-base which just gets revived by the before created full patch/refit-config.
function refitNullishValuesRecursively(target, source) {
if (Array.isArray(source) && Array.isArray(target)) {
source
.forEach((sourceItem, idx) => {
if (target[idx] == null) {
if (sourceItem != null) {
target[idx] = cloneDataStructure(sourceItem);
}
} else {
refitNullishValuesRecursively(target[idx], sourceItem);
}
});
} else if (
source && target &&
'object' === typeof source &&
'object' === typeof target
) {
Object
.entries(source)
.forEach(([key, sourceValue], idx) => {
if (target[key] == null) {
if (sourceValue != null) {
target[key] = cloneDataStructure(sourceValue);
}
} else {
refitNullishValuesRecursively(target[key], sourceValue);
}
});
}
return target;
}
function reviveNullishValuesRecursively(target, source) {
if (Array.isArray(target) && Array.isArray(source)) {
target
.forEach((targetItem, idx) => {
if (targetItem == null) {
target[idx] = cloneDataStructure(source[idx]) ?? targetItem;
} else {
reviveNullishValuesRecursively(targetItem, source[idx]);
}
});
} else if (
target && source &&
'object' === typeof target &&
'object' === typeof source
) {
Object
.entries(target)
.forEach(([key, targetValue], idx) => {
if (targetValue == null) {
target[key] = cloneDataStructure(source[key]) ?? targetValue;
} else {
reviveNullishValuesRecursively(targetValue, source[key]);
}
});
}
return target;
}
const cloneDataStructure =
('function' === typeof structuredClone)
&& structuredClone
|| (value => JSON.parse(JSON.stringify(value)));
const defaultConfig = {
first: {
visible: 'default.first.visible',
emoji: 'default.first.emoji',
style: 'default.first.style',
},
forward: {
visible: 'default.forward.visible',
emoji: 'default.forward.emoji',
style: 'default.forward.style',
},
removeBtn: {
visible: 'default.removeBtn.visible',
emoji: 'default.removeBtn.emoji',
style: 'default.removeBtn.style',
},
};
const currentConfig = {
first: {
visible: 'current.first.visible',
emoji: 'current.first.emoji',
style: 'current.first.style',
},
forward: {
visible: 'current.forward.visible',
emoji: null,
},
FOO: {
visible: 'current.FOO.visible',
emoji: 'current.FOO.emoji',
style: 'current.FOO.style',
}
};
function getConfiguration(baseConfig) {
return reviveNullishValuesRecursively(
cloneDataStructure(baseConfig),
refitNullishValuesRecursively(
cloneDataStructure(currentConfig),
defaultConfig,
),
);
}
console.log(
getConfiguration({
first: null,
forward: null,
removeBtn: null,
})
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
If the structure of your object is the one you presented you can do:
function normalize(input, defaultValue) {
// Loop on the outer keys
Object.keys(input).forEach(mainKey => {
// Loop on the inner keys
Object.keys(input[mainKey]).forEach(key => {
// set the value of the key as itself or default if null
input[mainKey][key] = input[mainKey]?.[key] ?? defaultValue[mainKey]?.[key]
})
})
return input;
}
Calling normalize(args, defaulttest) you will loop on each inner key, check if it exist and if it does not exist you substitute it with the default in the same path.
Example:
const x = {
a: {a1: '1', a2: '2'},
b: {b1: '1', b2: null}
}
const y = {b: {b2: '5'}}
console.log(normalize(x,y))
Output:
{
"a": {
"a1": "1",
"a2": "2"
},
"b": {
"b1": "1",
"b2": "5"
}
}
With this approach you must have the key in the args input. If the key is missing, it will not be substituted with the default. To make it work even with not-present keys you need to use a third structure with all the possible path for example.
I am attempting to interrogate a multi-dimensional object and return part of the object based on a variable available to me. I do not have access to many of ES6's object and array methods.
My object to interrogate looks like:
const myObj = {
blue: {
three: {
star: {
foo: "this object is what I want",
foo2: "this object is what I want"
}
}
}
}
As you can see there are three layers to this. I do not know what any of the keys may be with the exception of star. I know I want to return the value of this key if it's available.
In order to return the object for the key star I am currently using Object.keys().map() three times, which works but I feel like there must be a simpler solution. Below is what I have:
return Object.keys(myObj).map(function(colour) {
Object.keys(myObj[colour]).map(function(number) {
Object.keys(myObj[colour][number]).map(function(shape){
if (myObj[colour][number][shape] === "star") {
return myObj[colour][number][shape];
}
});
});
});
Is there something else I can use to step through this object until I hit my matching key? And then return the value of that key?
const myObj = {
blue: {
three: {
star: {
foo: "this object is what I want",
foo2: "this object is what I want"
}
}
}
}
You can iterate over all properties and check if that property is, what you want.. than you can build array from them or whatever you like. :)
const seeker = function (object) {
for (let property in object)
{
if (property === 'star')
{
console.log(property, object[property])
}
if (object[property] instanceof Object)
seeker(object[property])
}
}
Maybe you could try this:
function recursive(myObj) {
const result = [];
for (const key in myObj) {
if (key === 'star') {
result.push(myObj[key]);
}
if (typeof myObj[key] === 'object') {
result.push(recursive(myObj[key]).reduce((acc, val) => acc.concat(val), []));
}
}
return result.reduce((acc, val) => acc.concat(val), []);
}
I have a JS object like this:
var tenants = {
'first': {
'name': 'first',
'expired': 1
},
'second': {
'name': 'second'
}
}
And I'd like to delete the 'expired' property of tenant 'first', should I just do this?
delete tenants['first']['expired'];
Note: this question is more specific than the question: How do I remove a property from a JavaScript object?, in that my question focuses on the 'nested' part.
Yes. That would work.
delete tenants['first']['expired']; or delete tenants.first.expired;.
If you are deleting it only because you wanted to exclude it from JSON.stringify(), you can also just set it to undefined, like tenants['first']['expired'] = undefined;
If the property you want to delete is stored in a string, you can use this function
function deletePropertyPath (obj, path) {
if (!obj || !path) {
return;
}
if (typeof path === 'string') {
path = path.split('.');
}
for (var i = 0; i < path.length - 1; i++) {
obj = obj[path[i]];
if (typeof obj === 'undefined') {
return;
}
}
delete obj[path.pop()];
};
Example Usage
var tenants = {
'first': {
'name': 'first',
'expired': 1
},
'second': {
'name': 'second'
}
}
var property = 'first.expired';
deletePropertyPath(tenants, property);
If your app is using lodash, then _.unset is a safe way for deleting nested properties. You can specify nested keys without worrying about their existence.
let games = { 'hitman': [{ 'agent': { 'id': 47 } }] };
_.unset(games, 'hitman[0].agent.id');
_.unset(games, 'hitman[0].muffin.cupcake'); // won't break
further reading: https://lodash.com/docs/4.17.15#unset
I came up with this:
const deleteByPath = (object, path) => {
let currentObject = object
const parts = path.split(".")
const last = parts.pop()
for (const part of parts) {
currentObject = currentObject[part]
if (!currentObject) {
return
}
}
delete currentObject[last]
}
Usage:
deleteByPath({ "a" : { "b" : true }},"a.b")
If you want to delete a property with a particular name in an arbitrarily deep object, I would recommend that you use a battle-tested library. You can use DeepDash, an extension to Lodash.
// Recursively remove any "expired" properties
_.eachDeep(e, (child, prop, parent, ctx):boolean => {
if (prop === 'expired') {
delete parent[prop];
return false; // per docs, this means do not recurse into this child
}
return true;
});
And if you would rather have a new copy (rather than mutating the existing object), DeepDash also has an omitDeep function you can use that will return the new object.
If you have the path of the key separated by ., say first.expired in your case, you can do deleteKey(tenants, 'first.expired'):
const deleteKey = (obj, path) => {
const _obj = JSON.parse(JSON.stringify(obj));
const keys = path.split('.');
keys.reduce((acc, key, index) => {
if (index === keys.length - 1) {
delete acc[key];
return true;
}
return acc[key];
}, _obj);
return _obj;
}
let tenants = {
'first': {
'name': 'first',
'expired': 1
},
'second': {
'name': 'second'
}
};
const PATH_TO_DELETE = 'first.expired';
tenants = deleteKey(tenants, PATH_TO_DELETE);
console.log('DELETE SUCCESSFUL:', tenants);
With modern JS you can simple do it this way:
const tenants = {
first: {
name: 'first',
expired: 1
},
second: {
name: 'second'
}
}
delete tenants?.first?.expired;
delete tenants?.second?.expired;
delete tenants?.third?.expired;
console.log(tenants);
By using optional chaining you're able to safely try to remove nested properties on objects that might not exist.
Check the mdn site to check browser compatibility
NOTE: Optional chaining does also works with braces
What's a good and short way to remove a value from an object at a specific key without mutating the original object?
I'd like to do something like:
let o = {firstname: 'Jane', lastname: 'Doe'};
let o2 = doSomething(o, 'lastname');
console.log(o.lastname); // 'Doe'
console.log(o2.lastname); // undefined
I know there are a lot of immutability libraries for such tasks, but I'd like to get away without a library. But to do this, a requirement would be to have an easy and short way that can be used throughout the code, without abstracting the method away as a utility function.
E.g. for adding a value I do the following:
let o2 = {...o1, age: 31};
This is quite short, easy to remember and doesn't need a utility function.
Is there something like this for removing a value? ES6 is very welcome.
Thank you very much!
Update:
You could remove a property from an object with a tricky Destructuring assignment:
const doSomething = (obj, prop) => {
let {[prop]: omit, ...res} = obj
return res
}
Though, if property name you want to remove is static, then you could remove it with a simple one-liner:
let {lastname, ...o2} = o
The easiest way is simply to Or you could clone your object before mutating it:
const doSomething = (obj, prop) => {
let res = Object.assign({}, obj)
delete res[prop]
return res
}
Alternatively you could use omit function from lodash utility library:
let o2 = _.omit(o, 'lastname')
It's available as a part of lodash package, or as a standalone lodash.omit package.
With ES7 object destructuring:
const myObject = {
a: 1,
b: 2,
c: 3
};
const { a, ...noA } = myObject;
console.log(noA); // => { b: 2, c: 3 }
one line solution
const removeKey = (key, {[key]: _, ...rest}) => rest;
Explanations:
This is a generic arrow function to remove a specific key. The first argument is the name of the key to remove, the second is the object from where you want to remove the key. Note that by restructuring it, we generate the curated result, then return it.
Example:
let example = {
first:"frefrze",
second:"gergerge",
third: "gfgfg"
}
console.log(removeKey('third', example))
/*
Object {
first: "frefrze",
second: "gergerge"
}
*/
To add some spice bringing in Performance. Check this thread bellow
https://github.com/googleapis/google-api-nodejs-client/issues/375
The use of the delete operator has performance negative effects for
the V8 hidden classes pattern. In general it's recommended do not use
it.
Alternatively, to remove object own enumerable properties, we could
create a new object copy without those properties (example using
lodash):
_.omit(o, 'prop', 'prop2')
Or even define the property value to null or undefined (which is
implicitly ignored when serializing to JSON):
o.prop = undefined
You can use too the destructing way
const {remov1, remov2, ...new} = old;
old = new;
And a more practical exmple:
this._volumes[this._minCandle] = undefined;
{
const {[this._minCandle]: remove, ...rest} = this._volumes;
this._volumes = rest;
}
As you can see you can use [somePropsVarForDynamicName]: scopeVarName syntax for dynamic names. And you can put all in brackets (new block) so the rest will be garbage collected after it.
Here a test:
exec:
Or we can go with some function like
function deleteProps(obj, props) {
if (!Array.isArray(props)) props = [props];
return Object.keys(obj).reduce((newObj, prop) => {
if (!props.includes(prop)) {
newObj[prop] = obj[prop];
}
return newObj;
}, {});
}
for typescript
function deleteProps(obj: Object, props: string[]) {
if (!Array.isArray(props)) props = [props];
return Object.keys(obj).reduce((newObj, prop) => {
if (!props.includes(prop)) {
newObj[prop] = obj[prop];
}
return newObj;
}, {});
}
Usage:
let a = {propH: 'hi', propB: 'bye', propO: 'ok'};
a = deleteProps(a, 'propB');
// or
a = deleteProps(a, ['propB', 'propO']);
This way a new object is created. And the fast property of the object is kept. Which can be important or matter. If the mapping and the object will be accessed many many times.
Also associating undefined can be a good way to go with. When you can afford it. And for the keys you can too check the value. For instance to get all the active keys you do something like:
const allActiveKeys = Object.keys(myObj).filter(k => myObj[k] !== undefined);
//or
const allActiveKeys = Object.keys(myObj).filter(k => myObj[k]); // if any false evaluated value is to be stripped.
Undefined is not suited though for big list. Or development over time with many props to come in. As the memory usage will keep growing and will never get cleaned. So it depend on the usage. And just creating a new object seem to be the good way.
Then the Premature optimization is the root of all evil will kick in. So you need to be aware of the trade off. And what is needed and what's not.
Note about _.omit() from lodash
It's removed from version 5. You can't find it in the repo. And here an issue that talk about it.
https://github.com/lodash/lodash/issues/2930
v8
You can check this which is a good reading https://v8.dev/blog/fast-properties
As suggested in the comments above if you want to extend this to remove more than one item from your object I like to use filter. and reduce
eg
const o = {
"firstname": "Jane",
"lastname": "Doe",
"middlename": "Kate",
"age": 23,
"_id": "599ad9f8ebe5183011f70835",
"index": 0,
"guid": "1dbb6a4e-f82d-4e32-bb4c-15ed783c70ca",
"isActive": true,
"balance": "$1,510.89",
"picture": "http://placehold.it/32x32",
"eyeColor": "green",
"registered": "2014-08-17T09:21:18 -10:00",
"tags": [
"consequat",
"ut",
"qui",
"nulla",
"do",
"sunt",
"anim"
]
};
const removeItems = ['balance', 'picture', 'tags']
console.log(formatObj(o, removeItems))
function formatObj(obj, removeItems) {
return {
...Object.keys(obj)
.filter(item => !isInArray(item, removeItems))
.reduce((newObj, item) => {
return {
...newObj, [item]: obj[item]
}
}, {})
}
}
function isInArray(value, array) {
return array.indexOf(value) > -1;
}
My issue with the accepted answer, from an ESLint rule standard, if you try to destructure:
const { notNeeded, alsoNotNeeded, ...rest } = { ...ogObject };
the 2 new variables, notNeeded and alsoNotNeeded may throw a warning or error depending on your setup since they are now unused. So why create new vars if unused?
I think you need to use the delete function truly.
export function deleteKeyFromObject(obj, key) {
return Object.fromEntries(Object.entries(obj).filter(el => el[0] !== key))
}
with lodash cloneDeep and delete
(note: lodash clone can be used instead for shallow objects)
const obj = {a: 1, b: 2, c: 3}
const unwantedKey = 'a'
const _ = require('lodash')
const objCopy = _.cloneDeep(obj)
delete objCopy[unwantedKey]
// objCopy = {b: 2, c: 3}
For my code I wanted a short version for the return value of map() but the multiline/mutli operations solutions were "ugly". The key feature is the old void(0) which resolve to undefined.
let o2 = {...o, age: 31, lastname: void(0)};
The property stays in the object:
console.log(o2) // {firstname: "Jane", lastname: undefined, age: 31}
but the transmit framework kills it for me (b.c. stringify):
console.log(JSON.stringify(o2)) // {"firstname":"Jane","age":31}
I wrote big function about issue for me. The function clear all values of props (not itself, only value), arrays etc. as multidimensional.
NOTE: The function clear elements in arrays and arrays become an empty array. Maybe this case can be added to function as optional.
https://gist.github.com/semihkeskindev/d979b169e4ee157503a76b06573ae868
function clearAllValues(data, byTypeOf = false) {
let clearValuesTypeOf = {
boolean: false,
number: 0,
string: '',
}
// clears array if data is array
if (Array.isArray(data)) {
data = [];
} else if (typeof data === 'object' && data !== null) {
// loops object if data is object
Object.keys(data).forEach((key, index) => {
// clears array if property value is array
if (Array.isArray(data[key])) {
data[key] = [];
} else if (typeof data[key] === 'object' && data !== null) {
data[key] = this.clearAllValues(data[key], byTypeOf);
} else {
// clears value by typeof value if second parameter is true
if (byTypeOf) {
data[key] = clearValuesTypeOf[typeof data[key]];
} else {
// value changes as null if second parameter is false
data[key] = null;
}
}
});
} else {
if (byTypeOf) {
data = clearValuesTypeOf[typeof data];
} else {
data = null;
}
}
return data;
}
Here is an example that clear all values without delete props
let object = {
name: 'Semih',
lastname: 'Keskin',
brothers: [
{
name: 'Melih Kayra',
age: 9,
}
],
sisters: [],
hobbies: {
cycling: true,
listeningMusic: true,
running: false,
}
}
console.log(object);
// output before changed: {"name":"Semih","lastname":"Keskin","brothers":[{"name":"Melih Kayra","age":9}],"sisters":[],"hobbies":{"cycling":true,"listeningMusic":true,"running":false}}
let clearObject = clearAllValues(object);
console.log(clearObject);
// output after changed: {"name":null,"lastname":null,"brothers":[],"sisters":[],"hobbies":{"cycling":null,"listeningMusic":null,"running":null}}
let clearObject2 = clearAllValues(object);
console.log(clearObject2);
// output after changed by typeof: {"name":"","lastname":"","brothers":[],"sisters":[],"hobbies":{"cycling":false,"listeningMusic":false,"running":false}}