Pulling dynamic values from JavaScript object - javascript

I want to destructure a dynamic key from a object, where `key` is some pattern. There is counter appended to key.
var obj = {
"key2":{"left":{"content": "This data to be pulled"}},
"hasErrorcharges2":false,
"hasErrorcharges2_Card":""
}
const {key2: {left: content }} = obj;
Here key2 is dynamic. So we know that it will always start with key and the other values can be key0, key1, key3 and hence forth. How do we traverse in this case?
Things tried.
Match the if object has any key similar to it. and then return the matched key. but got true false
can't destructure dynamic prop. but in this we know a pattern
traverse through the object with dynamic property and get the value.
expecting to write a similar function like hasOwn() or hasOwnProperty

You can't do the destructuring until you know the name of the property. You can find it by using find on Object.keys (but keep reading for an alternative). Then you can use computed property notation to specify that name in the destructuring expression. (There's also a small error in that expression, see the highlighted bit below.)
const keyName = Object.keys(obj).find((key) => key.startsWith("key"));
if (keyName) {
const {
// vvvvvvvvv−−−−−−−−−−−−−−−−−−−−−−−−−− computed property notation
[keyName]: { left: { content } },
// ^−−−−−−−−−^−−−−− minor correction to destructuring
} = obj;
// ...
}
There I've just checked that the property name starts with key — you might want to beef up the condition in the find callback, but that's the general idea.
Live Example:
const obj = {
key2: { left: { content: "This data to be pulled" } },
hasErrorcharges2: false,
hasErrorcharges2_Card: "",
};
const keyName = Object.keys(obj).find((key) => key.startsWith("key"));
if (keyName) {
const {
[keyName]: { left: { content } },
} = obj;
console.log(`content = ${content}`);
}
That said, if you need to loop through the object properties anyway, it may not be worth setting yourself up for destructuring vs. just grabbing the property in a loop and breaking when you find it:
let content = null;
for (const key in obj) {
if (key.startsWith("key")) {
content = obj[key].left.content;
break;
}
}
if (content !== null) { // Valid only if we know content won't be `null` in the object
// ...
}
Live Example:
const obj = {
key2: { left: { content: "This data to be pulled" } },
hasErrorcharges2: false,
hasErrorcharges2_Card: "",
};
let content = null;
for (const key in obj) {
if (key.startsWith("key")) {
content = obj[key].left.content;
break;
}
}
if (content !== null) { // Valid only if we know content won't be `null` in the object
console.log(`content = ${content}`);
}
If you like, this:
content = obj[key].left.content;
could be:
({ content } = obj[key].left);
...which avoid repeating the identifier content. Or even:
({left: { content }} = obj[key]);
...though there's really no need to use the nested destructuring, it doesn't save you anything. :-)
(We need the () around it because otherwise the { at the beginning looks like the beginning of a block to the JavaScript parser.)

Related

Delete item from object by selector string

I am trying to delete an item from an object by passing a key to the method. For example I want to delete a1, and to do so I pass a.a1 to the method. It then should delete a1 from the object leaving the rest of the object alone.
This is the structure of the object:
this.record = {
id: '',
expiration: 0,
data: {
a: {
a1: 'Cat'
}
}
}
I then call this method:
delete(key) {
let path = key.split('.')
let data = path.reduce((obj, key) => typeof obj == 'object' ? obj[key] : null, this.record.data)
if(data) delete data
}
Like this:
let inst = new MyClass()
inst.delete('a.a1')
This however gives me the following error:
delete data;
^^^^
SyntaxError: Delete of an unqualified identifier in strict mode.
I assume that data is a reference still at this point, or is it not?
Maybe reduce isn't the right method to use here. How can I delete the item from the object?
Using your example, the value of data at the point where it is checked for truthiness is Cat, the value of the property you're trying to delete. At this point, data is just a regular variable that's referencing a string and it's no longer in the context of inst.
Here's a solution I managed to get to work using the one from your OP as the basis:
let path = key.split('.')
let owningObject = path.slice(0, path.length - 1)
.reduce((obj, key) => typeof obj == 'object' ? obj[key] : null, this.record.data)
if (owningObject) delete owningObject[path[path.length - 1]]
The main difference between this and what you had is that reduce operates on a slice of the path segments, which does not include the final identifier: This ends up with owningObject being a reference to the a object. The reduce is really just navigating along the path up until the penultimate segment, which itself is used as the property name that gets deleted.
For an invalid path, it bails out either because of the if (owningObject) or because using delete on an unknown property is a no-op anyway.
The solution I came up with which I am not super fond of but works, is looping over the items which will allow me to do long keys like this
a.a1
a.a1.a1-1
a.a1.a1-1.sub
The function then looks like this
let record = {
data: {
a: {
a1: 'Cat',
a2: {
val: 'Dog'
}
}
}
}
function remove(key) {
let path = key.split('.')
let obj = record.data
for (let i = 0; i < path.length; i++) {
if (i + 1 == path.length && obj && obj[path[i]]) delete obj[path[i]]
else if(obj && obj[path[i]]) obj = obj[path[i]]
else obj = null
}
}
// Removes `a.a1`
remove('a.a1')
console.log(JSON.stringify(record))
// Removes `a.a2.val`
remove('a.a2.val')
console.log(JSON.stringify(record))
// Removes nothing since the path is invalid
remove('a.a2.val.asdf.fsdf')
console.log(JSON.stringify(record))
You can delete keys using [] references.
var foo = {
a: 1,
b: 2
};
var selector = "a";
delete foo[selector];
console.log(foo);
I'm not sure if this helps you but it might help someone googling to this question.
Here's another method which is very similar to the OP's own solution but uses Array.prototype.forEach to iterate over the path parts. I came to this result independently in my attempt to wrap this up as elegantly as possible.
function TestRecord(id, data) {
let record = {
id : id,
data : data
};
function removeDataProperty(key) {
let parent = record.data;
let parts = key.split('.');
let l = parts.length - 1;
parts.forEach((p, i) => {
if (i < l && parent[p]) parent = parent[p];
else if (i == l && parent[p]) delete parent[p];
else throw new Error('invalid key');
});
}
return {
record : record,
remove : function(key) {
try {
removeDataProperty(key);
} catch (e) {
console.warn(`key ${key} not found`);
}
}
}
}
let test = new TestRecord('TESTA', {
a : { a1 : '1', a2 : '2' },
b : { c : { d : '3' } }
});
test.remove('a'); // root level properties are supported
test.remove('b.c.d'); // deep nested properties are supported
test.remove('a.b.x'); // early exit loop and warn that property not found
console.log(test.record.data);
The usage of throw in this example is for the purpose of breaking out of the loop early if any part of the path is invalid since forEach does not support the break statement.
By the way, there is evidence that forEach is slower than a simple for loop but if the dataset is small enough or the readability vs efficiency tradeoff is acceptable for your use case then this may be a good alternative.
https://hackernoon.com/javascript-performance-test-for-vs-for-each-vs-map-reduce-filter-find-32c1113f19d7
This may not be the most elegant solution but you could achieve the desired result very quickly and easily by using eval().
function TestRecord(id) {
let record = {
id : id,
data : {
a : {
a1 : 'z',
a2 : 'y'
}
}
};
return {
record : record,
remove : function (key) {
if (!key.match(/^(?!.*\.$)(?:[a-z][a-z\d]*\.?)+$/i)) {
console.warn('invalid path');
return;
} else {
let cmd = 'delete this.record.data.' + key;
eval(cmd);
}
}
};
}
let t = new TestRecord('TESTA');
t.remove('a.a1');
console.log(t.record.data);
I have included a regular expression from another answer that validates the user input against the namespace format to prevent abuse/misuse.
By the way, I also used the method name remove instead of delete since delete is a reserved keyword in javascript.
Also, before the anti-eval downvotes start pouring in. From: https://humanwhocodes.com/blog/2013/06/25/eval-isnt-evil-just-misunderstood/ :
...you shouldn’t be afraid to use it when you have a case where eval()
makes sense. Try not using it first, but don’t let anyone scare you
into thinking your code is more fragile or less secure when eval() is
used appropriately.
I'm not promoting eval as the best way to manipulate objects (obviously a well defined object with a good interface would be the proper solution) but for the specific use-case of deleting a nested key from an object by passing a namespaced string as input, I don't think any amount of looping or parsing would be more efficient or succinct.

How to remove the quotes when forming a javascript object?

I am creating a dynamic javascript object to query from the mongodb, and my code as follows,
_.each(separatedFilter, function (str) {
const filter = str.split('=');
console.log('filter is', JSON.stringify(filter));
if (filter[1] && filter[1].trim() !== '') {
var key = `${filter[0]}`;
var obj = {};
obj[key] = filter[1];
if (key = 'date.start:{$gte') {
key = '"date.start":{"$gte"';
}
if (key = 'date.end:{$lt') {
key = '"date.end":{"$lt"';
}
query.push(obj);
}
});
the above code creates a object as follows,
{ '$and':
[ { name: [Object] },
{},
{ '"date.start":{"$gte"': '2016-12-18T18:30:00.000Z',
'"date.end":{"$lt"': '2016-12-18T18:30:00.000Z' }
] }
in the above object i could see ' at the start and end of date.start and date.end. whereas i just want it to be,
{ '$and':
[ { name: [Object] },
{},
{ "date.start":{"$gte": '2016-12-18T18:30:00.000Z',
"date.end":{"$lt": '2016-12-18T18:30:00.000Z' }
] }
You cannot define a value (the nested object) via a key, nor can you partially define an object with a key (i.e. $gte) but without a value.
A fix would look something like this:
if (key === 'date.start:{$gte') {
obj['date.start'] = { '$gte' : null };
}
if (key === 'date.end:{$lt') {
obj['date.start'] = { '$lt' : null };
}
obj[key] = filter[1];
This assumes that null will be replaced with the appropriate timestamps somewhere else in the program and then you can serialize that object to a string before sending the query.
Explanation
These lines have a number of problems:
if (key = 'date.start:{$gte') {
key = '"date.start":{"$gte"';
}
if (key = 'date.end:{$lt') {
key = '"date.end":{"$lt"';
}
They are inserting quotes into the key for some reason. Probably just a mistake.
They are assigning to key within the conditional statement, rather than doing a comparison. This will have the effect of always executing the code within the if block, because the string being assigned and returned is always truthy.
They come after the key and its value are already assigned to the object via obj[key] = filter[1]. Since the assignment has already run by the time the if statements do, they have no chance to affect the key that is used in the assignment.
Also, this line:
var key = `${filter[0]}`;
Can be simplified, as it is the same as:
var key = filter[0];
Or even better with let and destructuring:
let [key] = filter;

Get an object just by a property value

Let´s assume I have an object property which is passed into a function. In this case 'name' is filled with 'myObject.name' (which has the value 'Tom') - so basically 'Tom' gets passed into the function as the 'name'
function(name) {
do something //non-essential for my question
}
Is it possible to get the object, where 'Tom' is the property of, just by having the information 'Tom'? Basically I´m looking to get myObject.
Thanks :)
No, that's not possible.
All that the function knows is that one of its parameters was pointed to the string "Tom", not what else points to that string somewhere else in memory.
You can store objects within an array, filter the array to match property name of object to parameter passed to function using for..of loop, Object.entries(), which returns an array of property, values of an object.
const data = Array();
const setObjectPropertyName = _name => {
data.push({[_name]:_name});
return data
}
const getObjectByPropertyName = prop => {
let res = `${prop} property not found in data`;
for (let obj of data) {
for (let [key] of Object.entries(obj)) {
if(key === prop) return obj;
}
}
return res;
}
let s = setObjectPropertyName("Tom");
let g = getObjectByPropertyName("Tom");
let not = getObjectByPropertyName("Tome");
console.log(s,"\n", g, "\n", not);
Disclaimer: you absolutely should not do this. I'm only posting this because it is in fact possible (with some caveats), just really not advisable.
Going on the assumption that this is running in the browser and it's all running in the global scope (like in a script tag), you could technically iterate over the window object, check any objects in window for a name property and determine if their name property matches the name passed to your function.
var myObject = {
name: 'Tom',
thisIs: 'so awful',
imSorry: true,
};
function doSomethingWithName(name) {
for (var obj in window) {
var tmp = window[obj];
if (Object(tmp) === tmp && tmp.name === name) {
return tmp;
}
}
}
console.log(doSomethingWithName(myObject.name));

How to access a nested property, defined as a string, without using eval?

I have an object which may or may not have nested objects and properties, and I want to access them using a string. Here's an example...
var obj = {
inside: {
value: 10,
furtherInside: {
value: 100
}
}
// may contain other objects, properties, etc.
};
function getObjProperty(str) {
return eval("obj." + str);
}
getObjProperty("inside.value"); // returns 10
getObjProperty("inside.furtherInside.value"); // returns 100
...But I'd like a solution that doesn't use eval.
How can this be done without using eval? I'm looking for the best/optimal/fastest solution.
How about something like
function getObjectProperty(obj, str) {
var props = str.split('.')
var result = obj;
for(var i = 0; i < props.length; i++)
result = result[props[i]];
return result;
}
This code assumes your strings are always valid and the object passed into getObjectProperty has properties that nest to the level you target, but it avoids eval. You could make it more robust with checks for undefined, but that may be overkill for what you need.
Test code, using your example:
var a = {
inside: {
value: 10,
furtherInside: {
value: 100
}
}
// may contain other objects, properties, etc.
};
console.log(getObjProperty(a, "inside.value")); // prints 10
console.log(getObjProperty(a, "inside.furtherInside.value")); // prints 100
You can use the brackets notation:
var obj = {
inside: {
value: 10,
furtherInside: {
value: 100
}
}
// may contain other objects, properties, etc.
};
alert(obj['inside']['furtherInside']['value']);
Then you may even use string properties like "my property":
var obj = {
"my property": 10
};
obj["my property"];
EDIT:
This is an approach (using brackets notation) to what you are asking for:
String.prototype.getVal = function(elem) {
var segments = this.split('.');
for (var i = 0; i < segments.length; i++) {
elem = elem[segments[i]];
}
return elem;
}
var obj = {
inside: {
value: 10,
furtherInside: {
value: 100
}
}
// may contain other objects, properties, etc.
};
console.log("inside.furtherInside.value".getVal(obj));
console.log("inside.value".getVal(obj));
http://jsfiddle.net/luismartin/kphtqd54
Since this method getVal() is being assigned to the String prototype, you may use it anywhere, and I think the implementation is pretty neat and fast. I hope this approach also helps getting rid of the negative vote :/
This is what I came up with, using some recursiveness...
function getObjProperty(obj, props) {
if (typeof props === 'string') {
if (props.indexOf('.') == -1) {
return obj[props];
} else {
props = props.split('.');
}
}
if (props.length == 1) {
return obj[props[0]];
} else if (props.length > 1) {
var top = props.shift();
return getObjProperty(obj[top], props);
} else {
return obj;
}
}
http://jsfiddle.net/0em2f6k6/
...But it's not as fast as a simple for-loop. http://jsperf.com/0em2f6k6
Although not vanilla JavaScript, another possibility is to use lodash's _.get function: https://lodash.com/docs#get.
_.get(obj, "inside.furtherInside.value");
It essentially does the same as #analytalica 's solution, except using a while loop (see the baseGet function in the lodash code), but it also allows strings or arrays (using the toPath function), and allows you to include a default.

More elegant way to get nested object properties by name?

I have this working code, which retrieves the names of object properties from a JS object which (unfortunately!) is out of my scope. So I cannot change how this object is built. But I want to (and do) extract the names of the properties, that are marked as true, as an array, to be able to handle this object easier.
Object:
{
group1: {
foo: true,
itemFoo: "Name of foo", // This is what I want, because foo is true
bar: false,
itemBar: "Name of bar", // I dont want this, bar is false
// ...
},
group2: {
baz: true,
itemBaz: "Name of baz", // I want this too
// ...
},
uselessProp1: "not an object",
// ...
}
Working Code:
var items = [];
for (var m in obj) {
if (typeof obj[m] == 'object') {
for (var n in obj[m]) {
if (obj[m][n] === true) {
items.push(obj[m]['item' + (n.charAt(0).toUpperCase() + n.slice(1))]);
}
}
}
}
My question is: does someone know a more elegant way of achieving this traversal with underscore.js or plain node.js or any other library? I did experiments with _.filter, but did not come up with a solution.
Something like this?
var result = [];
_.chain(obj).filter(_.isObject).each(function(t) {
_(t).each(function(val, key) {
if(val === true)
result.push(t['item' + key.charAt(0).toUpperCase() + key.substr(1)])
})
})
This is the solution I've come so far:
http://jsfiddle.net/kradmiy/28NZP/
var process = function (obj) {
var items = [];
var objectProperties = _(obj).each(function (rootProperty) {
// exit from function in case if property is not an object
if (!_(rootProperty).isObject()) return;
_(rootProperty).each(function (value, key) {
// proceed only if property is exactly true
if (value !== true) return;
var searchedKey = 'item' + (key.charAt(0).toUpperCase() + key.slice(1));
// check that parent has this property...
if (rootProperty.hasOwnProperty(searchedKey)) {
// ...and push that to array
items.push(rootProperty[searchedKey]);
}
});
});
return items;
};
I would like to point out something :
Micha’s Golden Rule
Micha Gorelick, a data scientist in NYC, coined the following rule:
Do not store data in the keys of a JSON blob.
Your JSON should use :
{//group1
groupname:"group1",
items :[
{//item1
itemcheck:true,
itemname:'itemBar'
},
...
]
},
...
If you store itemname in key. You will have problem when traversing the JSON, because your 'itemFoo' would be using 'foo'(indirectly) to get its value. Your data structure, is the problem here. Searching your JSON is tricky. Once you follow the rule, your code will be elegant automatically.

Categories