Say I have this function signature:
export const readVariableProps = function(obj: Object, props: Array<string>) : any {
// props => ['a','b','c']
return obj['a']['b']['c'];
}
obviously, props is a variable length array, with an unknown list or properties to read from the given object.
is the only way to get this kind of dynamic behavior to use eval()?
How can I do this?
To get the equivalent of return obj['a']['b']['c']; where 'a', 'b' and 'c' are the values in the array like you show in your question, you can do something like this (you may have to convert some details to typeScript):
export const readVariableProps = function(obj: Object, props: Array<string>) : any {
return props.reduce(function(prior, next) {
return prior[next];
}, obj);
}
FYI, this type of scenario is exactly what .reduce() was designed for -
accumulating a value that is built by visiting every item in an array.
This will throw if anything other than the last property in the array does not exist.
You can use a for..of loop to set a variable to each nested property
const props = ['a','b','c'];
const obj = {a:{b:{c:123}}};
let res;
for (let prop of props) res = !res ? obj[prop] : res[prop];
console.log(res);
Related
I want it to be the case that when I instantiate an instance of the class TableCategory, an object is created using the data passed in at instantiation. I want this to happen automatically at instantiation. I don't want have to instantiate the class and then call a method that creates the object. This seems unnecessary cumbersome.
I am using a class because I then want to manipulate the resultant object using getters and setters for multiple instantiations. (In case you wonder why I'm not just using an object in the first place.)
I'm not 100% clear on classes in JS so I'm not sure where I'm going wrong. Please note the object creation is a the product of a function it takes an array passed in at instantiation and an array that is native to the class.
Here's my class:
export class TableCategory {
constructor(categoryValues = []) {
this.categoryValues = categoryValues;
this.categoryKeys = ['alpha','beta','gamma', 'delta'];
this.categoryData = this.categoryKeys.forEach(function(key, i) {
return this.categoryData[key] = this.categoryValues[i];
});
}
}
Then, for example:
const foo = new TableCategory(['a'. 'b', 'c', 'd']);
console.log(foo.categoryData.beta); // b
Perhaps I need to use static ? Not sure, grateful for any help
forEach() doesn't return anything. Create an empty categoryData object, and then fill it in in the forEach loop.
Also, you need to use an arrow function to be able to access this in the callback function.
class TableCategory {
constructor(categoryValues = []) {
this.categoryValues = categoryValues;
this.categoryKeys = ['alpha', 'beta', 'gamma', 'delta'];
this.categoryData = {};
this.categoryKeys.forEach((key, i) =>
this.categoryData[key] = this.categoryValues[i]
)
}
}
const foo = new TableCategory(['a', 'b', 'c', 'd']);
console.log(foo.categoryData.beta); // b
forEach does not return anything else than undefined. You can still get the desired object in a functional way, but with Object.fromEntries.
Also, as you say you use a class because you intend to mutate the instance(s) with getters and setters, I don't think it is a good idea to still store the values and keys separately from the third property, which has all key/value information.
You could for instance do this:
class TableCategory {
constructor(values = []) {
this._obj = Object.fromEntries(['alpha','beta','gamma', 'delta']
.map((key, i) => [key, values[i]]));
}
get values() {
return Object.values(this._obj);
}
get keys() {
return Object.keys(this._obj);
}
}
let obj = new TableCategory(["this", "is", "a", "test"]);
console.log(obj.values);
How do I make an interface that’s distinct but allows me to index it’s values:
export const convertKeyNames = (Doc: MyDoc) => {
return Object.keys(Doc).reduce((doc, key) => {
return {
[key[0].toLowerCase() + key.slice(1)]: Doc[key],
};
}, {});
};
I get a typescript error for this saying:
Expression of type 'string' can't be used to index type 'MyDoc'
I don't want to convert MyDoc into a generic interface for an object with string indexes because it's being used further up the chain.
I want to retain the MyDoc interface.
Also as a side note, this makes no sense to me. Why can't I use the object's own keys to index it with?
It sounds like you're trying to just transform the keys of the input object. Consider creating the new object by mapping the object's entries instead:
export const convertKeyNames = (Doc: MyDoc) => (
Object.fromEntries(
Object.entries(Doc).map(
([key, val]) => [key[0].toLowerCase() + key.slice(1), val]
)
)
);
Why can't I use the object's own keys to index it with?
For some strange reason, Object.keys returns Array<string>, rather than Array<keyof param>. Easiest alternative is to use Object.entries instead, to extract the key and value at once.
TLDR Edit: I was confusing object destructuring w/ arrays and spread syntax. Below edit does the trick to recursively unpack the nested object.
let nextLevel = Object.values(obj)
return goingDeep(...nextLevel)
Following is the original question, which I'll leave up in case another noob such as myself runs into this, help save them from the downvotes ;p
Attempt to destructure a nested object recursively returns undefined. Putting that aside simply trying to destructure the same object into different variables returns undefined.
Keeping it simple, just assuming a single key:value (object) per layer so no need to iterate.
const container = {container1: {container2 : {container3: {container4: 'core'}}}}
Putting recursion aside for the moment the following results with simply two different destructuring assignments...
const {level1} = container
console.log(level1) // => container1: {container2:{ etc
const {level1_different} = container
console.log(level1_different) // => undefined
this is what I attempted with the recursion
const goingDeep = (obj) => {
if (obj.hasOwnProperty('container4')){
obj.container4 = 'found'
return obj
} else {
// let {nextLevel} = obj /no good
// return goingDeep(nextLevel) /no good
let nextLevel = Object.values(obj)
return goingDeep(...nextLevel)
}
}
originally had the destructuring at the parameter goingDeep = ({obj}) which I have used successfully for arrays so clearly I'm misunderstanding something(s) fundamental to destructuring objects
You are, indeed, misunderstanding how destructing works. If you have an object, destructing lets you create new variables with the same names as the properties in an object.
let obj = {
prop1: 'value1',
prop2: 'value2',
};
let {prop1, prop2, prop3} = obj;
console.log(prop1, prop2, prop3)
let {nextLevel} = obj
Is the same as
let nextLevel = obj.nextLevel;
I think you might be misunderstanding what destructing does.
To be recursive you will need to dynamically search all properties.
const goingDeep = obj => Object.getOwnPropertyNames(obj).reduce(
(prop, result) => result === null ? (obj[prop] === 'core' ? obj : goingDeep(obj[prop])) : result, null
);
So I'm in a unique situation where I have two objects, and I need to compare the keys on said objects to make sure they match the default object. Here's an example of what I'm trying to do:
const _ = require('lodash');
class DefaultObject {
constructor(id) {
this.id = id;
this.myobj1 = {
setting1: true,
setting2: false,
setting3: 'mydynamicstring'
};
this.myobj2 = {
perm1: 'ALL',
perm2: 'LIMITED',
perm3: 'LIMITED',
perm4: 'ADMIN'
};
}
}
async verifyDataIntegrity(id, data) {
const defaultData = _.merge(new DefaultObject(id));
if (defaultData.hasOwnProperty('myoldsetting')) delete defaultData.myoldsetting;
if (!_.isEqual(data, defaultData)) {
await myMongoDBCollection.replaceOne({ id }, defaultData);
return defaultData;
} else {
return data;
}
}
async requestData(id) {
const data = await myMongoDBCollection.findOne({ id });
if (!data) data = await this.makeNewData(id);
else data = await this.verifyDataIntegrity(id, data);
return data;
}
Let me explain. First, I have a default object which is created every time a user first uses the service. Then, that object is modified to their customized settings. For example, they could change 'setting1' to be false while changing 'perm2' to be 'ALL'.
Now, an older version of my default object used to have a property called 'myoldsetting'. I don't want newer products to have this setting, so every time a user requests their data I check if their object has the setting 'myoldsetting', and if it does, delete it. Then, to prevent needless updates (because this is called every time a user wants their data), I check if it is equal with the new default object.
But this doesn't work, because if the user has changed a setting, it will always return false and force a database update, even though none of the keys have changed. To fix this, I need a method of comparing the keys on an object, rather any the keys and data.
That way, if I add a new option to DefaultObject, say, 'perm5' set to 'ADMIN', then it will update the user's object. But, if their object has the same keys (it's up to date), then continue along your day.
I need this comparison to be deep, just in case I add a new property in, for example, myobj1. If I only compare the main level keys (id, myobj1, myobj2), it won't know if I added a new key into myobj1 or myobj2.
I apologize if this doesn't make sense, it's a very specific situation. Thanks in advance if you're able to help.
~~~~EDIT~~~~
Alright, so I've actually come up with a function that does exactly what I need. The issue is, I'd like to minify it so that it's not so big. Also, I can't seem to find a way to check if an item is a object even when it's null. This answer wasn't very helpful.
Here's my working function.
function getKeysDeep(arr, obj) {
Object.keys(obj).forEach(key => {
if (typeof obj[key] === 'object') {
arr = getKeysDeep(arr, obj[key]);
}
});
arr = arr.concat(Object.keys(obj));
return arr;
}
Usage
getKeysDeep([], myobj);
Is it possible to use it without having to put an empty array in too?
So, if I understand you correctly you would like to compare the keys of two objects, correct?
If that is the case you could try something like this:
function hasSameKeys(a, b) {
const aKeys = Object.keys(a);
const bKeys = Object.keys(b);
return aKeys.length === bKeys.length && !(aKeys.some(key => bKeys.indexOf(key) < 0));
}
Object.keys(x) will give you all the keys of the objects own properties.
indexOf will return a -1 if the value is not in the array that indexOf is being called on.
some will return as soon as the any element of the array (aKeys) evaluates to true in the callback. In this case: If any of the keys is not included in the other array (indexOf(key) < 0)
Alright, so I've actually come up with a function that does exactly what I need. The issue is, I'd like to minify it so that it's not so big. Also, I can't seem to find a way to check if an item is a object even when it's null.
In the end, this works for me. If anyone can improve it that'd be awesome.
function getKeysDeep(obj, arr = []) {
Object.keys(obj).forEach(key => {
if (typeof obj[key] === 'object' && !Array.isArray(obj[key]) && obj[key] !== null) {
arr = this.getKeysDeep(obj[key], arr);
}
});
return arr.concat(Object.keys(obj));
}
getKeysDeep(myobj);
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));