Let's say I have input object like this one:
const obj = {
deep: {
someKey: 'someValue1!',
veryDeep: {
someOtherKey: 'someOtherValue'
}
},
deep2: {
aa: 'bba'
}
}
I want to write a function that will take this object as first argument and key string as second argument. Then it's gonna loop through obj with recursion and find the value and return it. So if I name this function getObjectValueOfKey it will be called like this: getObjectValueOfKey(obj, 'aa') and it will return 'bba'.
I think I'm close with this code:
const getObjectValueOfKey = (obj, key) => {
const keys = Object.keys(obj)
for (let currKey of keys) {
if (typeof obj[currKey] === 'object') {
getObjectValueOfKey(obj[currKey], key)
}
if (currKey === key) {
return obj[key]
}
}
}
const res = getObjectValueOfKey(obj, 'aa')
console.log('res', res)
but for some unknown to me reasons res is undefined.
you were pretty close, the recursion was good but the problem is related with what all the paths in your code doesn't return, actually the only path that return is when you found the key in the first level of the object, after you made the recursion and found the result but nothing was returned, the code reach the end the of function and javascript return by default undefined if any return is not found, to solve the problem a decide to store the results found in the recursion in a variable and return that value at the end of the function.
const getObjectValueOfKey = (obj, key, found) => {
const keys = Object.keys(obj);
let result = undefined;
result ||= found;
for (let currKey of keys) {
if (typeof obj[currKey] === "object") {
result ||= getObjectValueOfKey(obj[currKey], key, result);
}
if (currKey === key) {
return obj[key];
}
}
return result;
};
const res = getObjectValueOfKey(obj, "aa", undefined);
console.log("res", res);
Related
I have a very very very deep nested object state.
and i want to change all id properties at once with lodash cloneDeepWith methods.
i'm using cloneDeepWith and only works on first match.
if i dont return the modified object then it won't modifiy anything.
and if i return the value i think the function stops.
the function its working ok but the only problem is that only will run once.
const handleChangeIds = (value) => {
if (value === sections) {
const modifiedObject = cloneDeepWith(value, (sectionsValue) => {
if (sectionsValue && Object.hasOwn(sectionsValue, 'id')) {
const clonedObj = cloneDeep(sectionsValue);
clonedObj.id = generateObjectId();
return clonedObj;
// I Also Tried sectionsValue = clonedObj; its the same behavior
}
});
return modifiedObject;
}
};
const DuplicateSection = () => {
console.log('Original Store', form);
const store = cloneDeepWith(form, handleChangeIds);
console.log('Modified', store)
};
For those who want to achieve same thing like me.
I had a super deep nested object for form. and that form had a repeatable functionality.
and i needed to do two thing in generating another form.
generate new Id for every field Id.
clear the input Value.
I solved my problem like this
and it works perfectly for a super deep nested object.
import cloneDeepWith from 'lodash/cloneDeepWith';
const clearInputAndChangeId = (sections: FormSectionProps): FormSectionProps => {
return cloneDeepWith(sections, (value, propertyName, object) => {
if (propertyName === 'id') return generateObjectId();
if (propertyName === 'selected') return false;
if (propertyName === 'checked') return false;
if (propertyName === 'value') {
if (object.type === 'file') return [];
if (object.type === 'checkbox/rating') return 1;
return '';
}
});
};
I want to proxy localStorage setters and getters to parse objects and save them to storage on assignment like I use a regular object.
Pretty straight forward when saving single KV items but complicated(for me) when trying to update a nested object in the storage.
The problem I have is how to get the correct position in the object using the target parameter.
To overcome this problem, I prototyped the proxy object back to localStorage and parse it every time the proxy calls set.
It works like expected but doesn't look like the proper way to do it.
Any advice be appreciated.
Storage.prototype.proxy = {}
Storage.prototype.getAll = function () {
data = {};
Object.keys(this).forEach(key => {
try {
data[key] = JSON.parse(this[key])
} catch {
data[key] = this[key]
}
});
this.proxy = data;
return this.proxy;
}
Storage.prototype.update = function () {
this.clear();
obj = this.proxy;
Object.keys(obj).forEach(key => {
this.setItem(key, JSON.stringify(obj[key]));
});
}
Storage.prototype.dynamic = new Proxy(localStorage.getAll(), handler = {
get(target, prop) {
if (prop == 'isProxy') return true;
if (typeof target[prop] == 'undefined') return;
if (!target[prop].isProxy && typeof target[prop] === 'object') {
target[prop] = new Proxy(target[prop], handler);
}
return Reflect.get(...arguments);
},
set() {
Reflect.set(...arguments);
localStorage.update();
return true;
},
deleteProperty(target, prop) {
if (prop in target) {
delete target[prop];
localStorage.update();
}
}
});
const storage = localStorage.dynamic;
storage.new = {}; //create new key in storage that hold an object
storage.new.one = 1; //create new object in and assign 1 to it
console.log(storage.new.one); // prints 1
I am setting up a database package in Node.js and would like to not have separate functions for writing to the database like this:
write(key, val) and write({key: val, key2: val2}). I've seen other solutions on Stack Overflow and other websites and would like to have the simplest solution so my function would "know" whether it was a key, val pair or a JSON object. For example:
if (argtype == "kvp") { // key val pair
databaseJSON[key] = val;
flushToDB(databaseJSON);
} else {
let j = databaseJSON;
for (let i in Object.values(obj)) j[Object.keys(obj)[i]] = Object.values(obj)[i];
flushToDB(databaseJSON);
}
Thank you!
If two arguments are passed, just assign the val to the key property of the database, otherwise Object.assign the one argument to the database, to put all of its properties and values from the passed object to the database:
function write(key, val) {
if (val !== undefined) {
database[key] = val;
} else {
Object.assign(database, key);
}
flushToDB(database)
}
const database = {};
const flushToDB = db => console.log('flushing');
function write(key, val) {
if (val !== undefined) {
database[key] = val;
} else {
Object.assign(database, key);
}
flushToDB(database)
}
write('key', 'val');
console.log('db:', database);
write({ key2: 'val2', key3: 'val3' });
console.log('db:', database);
Because the database here is a plain object, not JSON (something in JSON format is a string, which is not the case here), better to call it database rather than databaseJSON. (See There's no such thing as a "JSON Object")
You can use this.
function foo() {
if (arguments.length == 1) {
// use your object code on arguments[0]
let j = databaseJSON;
let obj = arguments[0]
for (let i in Object.values(obj))
j[Object.keys(obj)[i]] = Object.values(obj)[i];
flushToDB(databaseJSON);
} else {
//user arguments[0] and arguments[1] for you key value
databaseJSON[arguments[0]] = arguments[1];
flushToDB(databaseJSON);
}
}
You can also throw error if arguments.length == 0
(I couldn't find an open Lodash Slack channel, that's why I'm posting here.)
Could you please tell me why the partialRight in this fiddle seems to do nothing? The correctRenameKeys function correctly renames the key in the supplied object, but the wrongRenameKeys function - which should do exactly the same - doesn't.
Please open the JavaScript console in your browser to see logs when running the fiddle. I tested it in Chrome.
const renameKeysOfOneObject = (object, keyMappings) => {
return _.reduce(object, function(result, value, key) {
key = keyMappings[key] || key;
result[key] = value;
return result;
}, {});
};
const correctRenameKeys = (objects, keyMappings) => {
const keysRenamer = object => renameKeysOfOneObject(object, keyMappings);
return _.map(objects, keysRenamer);
};
const wrongRenameKeys = (objects, keyMappings) => {
const keysRenamer = _.partialRight(renameKeysOfOneObject, keyMappings);
return _.map(objects, keysRenamer);
};
const object = {keyToBeRenamed: 'someValue'};
const objects = [object];
const keyMapping = {keyToBeRenamed: 'newKeyName'};
const correctlyRenamed = correctRenameKeys(objects, keyMapping);
const wronglyRenamed = wrongRenameKeys(objects, keyMapping);
console.assert(_.isEqual(correctlyRenamed, wronglyRenamed),
"The two objects should be equal. " +
"The 'keyToBeRenamed' key should have been renamed to 'newKeyName'.");
console.log(correctlyRenamed);
console.log(wronglyRenamed);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
There are two supporting statements from the lodash documentation that can answer your question:
lodash#partialRight
This method is like _.partial except that partially applied arguments
are appended to the arguments it receives.
lodash#map
Creates an array of values by running each element in collection thru
iteratee. The iteratee is invoked with three arguments: (value,
index|key, collection).
Notice that there are three arguments passed in a lodash#map iteratee, and since keyRenamer is passed as it's iteratee, then we can conclude that the invocation signature would look like this:
keyRenamer(value, index, collection, keyMappings);
If you really want to achieve the effect of having the second argument of the function renameKeysOfOneObject to be partially applied then use lodash#partial.
const wrongRenameKeys = (objects, keyMappings) => {
const keysRenamer = _.partial(renameKeysOfOneObject, _, keyMappings);
return _.map(objects, keysRenamer);
};
const renameKeysOfOneObject = (object, keyMappings) => {
return _.reduce(object, function(result, value, key) {
key = keyMappings[key] || key;
result[key] = value;
return result;
}, {});
};
const correctRenameKeys = (objects, keyMappings) => {
const keysRenamer = object => renameKeysOfOneObject(object, keyMappings);
return _.map(objects, keysRenamer);
};
const wrongRenameKeys = (objects, keyMappings) => {
const keysRenamer = _.partial(renameKeysOfOneObject, _, keyMappings);
return _.map(objects, keysRenamer);
};
const object = {keyToBeRenamed: 'someValue'};
const objects = [object];
const keyMapping = {keyToBeRenamed: 'newKeyName'};
const correctlyRenamed = correctRenameKeys(objects, keyMapping);
const wronglyRenamed = wrongRenameKeys(objects, keyMapping);
console.assert(_.isEqual(correctlyRenamed, wronglyRenamed),
"The two objects should be equal. " +
"The 'keyToBeRenamed' key should have been renamed to 'newKeyName'.");
console.log(correctlyRenamed);
console.log(wronglyRenamed);
.as-console-wrapper{min-height:100%;top:0}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
I am trying to recursively build an object with a tree of properties based on a MongoDB-ish selector "top.middle.bottom". There are some underscorejs helpers as well:
function setNestedPropertyValue(obj, fields, val) {
if (fields.indexOf(".") === -1) {
// On last property, set the value
obj[fields] = val;
return obj; // Recurse back up
} else {
var oneLevelLess = _.first(fields.split("."));
var remainingLevels = _.rest(fields.split(".")).join(".");
// There are more property levels remaining, set a sub with a recursive call
obj[oneLevelLess] = setNestedPropertyValue( {}, remainingLevels, val);
}
}
setNestedPropertyValue({}, "grandpaprop.papaprop.babyprop", 1);
Desired:
{
grandpaprop: {
papaprop: {
babyprop: 1
}
}
}
Outcome:
undefined
Helps and hints would be appreciated.
Instead of recursion I would choose for an iterative solution:
function setNestedPropertyValue(obj, fields, val)
{
fields = fields.split('.');
var cur = obj,
last = fields.pop();
fields.forEach(function(field) {
cur[field] = {};
cur = cur[field];
});
cur[last] = val;
return obj;
}
setNestedPropertyValue({}, "grandpaprop.papaprop.babyprop", 1);
EDIT
And here is another version thanks to the suggestions by Scott Sauyet:
function setPath(obj, [first, ...rest], val) {
if (rest.length == 0) {
return {...obj, [first]: val}
}
let nestedObj = obj[first] || {};
return {...obj, [first]: setPath(nestedObj, rest, val)};
}
function setNestedPropertyValue(obj, field, val) {
return setPath(obj, field.split('.'), val);
}
// example
let test_obj = {};
test_obj = setNestedPropertyValue(test_obj, "foo.bar.baz", 1);
test_obj = setNestedPropertyValue(test_obj, "foo.bar.baz1", 1);
// will output {"foo":{"bar":{"baz":1,"baz1":1}}}, while in the original version only "baz1" will be set
console.log(JSON.stringify(test_obj));
It's plain javascript
It only appends properties and will not override a top level object
setNestedPropertyValue() does not mutate the passed object (although keep in mind it only returns a shallow copy of the object, so some properties may be shared references between the original object and the new one)
I know this is old, but I needed exactly that kind of function and wasn't happy with the implementation, so here is my version:
function setNestedPropertyValue(obj, field, val) {
if (field.indexOf(".") === -1) {
obj[field] = val;
} else {
let fields = field.split(".");
let topLevelField = fields.shift();
let remainingFields = fields.join(".");
if (obj[topLevelField] == null) {
obj[topLevelField] = {};
}
setNestedPropertyValue(obj[topLevelField], remainingFields, val);
}
}
// example
let test_obj = {};
setNestedPropertyValue(test_obj, "foo.bar.baz", 1);
setNestedPropertyValue(test_obj, "foo.bar.baz1", 1);
// will output {"foo":{"bar":{"baz":1,"baz1":1}}}, while in the original version only "baz1" will be set
console.log(JSON.stringify(test_obj));
It's plain javascript
It only appends properties and will not override a top level object
setNestedPropertyValue() does not return the object so it is clear that it mutates the passed object
As mentioned by Jack in the question, I was not returning my object in the last line in the else statement. By adding this, it is now working:
obj[oneLevelLess] = setNestedPropertyValue( {}, remainingLevels, val);
return obj; // Add this line
}