Lodash - dynamically update parameters of a complex object using lodash - javascript

im trying to set values to an object dynamically passing the attribute that i need to set the value, for example. i need to set name for the object so i need to find this property in the object and set value 'new name'. but some property can be nested for example address.city.name so basically what i need to do is find where the property city is and set the new value to it. if i have one level i could do it easily using something like object[attribute] = newValue;
but sometime the property can be in nested object
object[path][attribute] = newValue;
i was able to accomplish this using the function bellow, but i would to know if its possible do this using lodash. I didn't find any function that find the way of the property
onEditDataCallback={(rowData, columnId, newValue) => {
const path = findPath(rowData, columnId);
if (path) {
rowData[path][columnId] = newValue;
} else {
rowData[columnId] = newValue;
}
return {
...rowData
};
}}
const findPath = (ob, key) => {
const path = [];
const keyExists = (obj) => {
if (!obj || (typeof obj !== 'object' && !Array.isArray(obj))) {
return false;
} else if (obj.hasOwnProperty(key)) {
return true;
} else if (Array.isArray(obj)) {
let parentKey = path.length ? path.pop() : '';
for (let i = 0; i < obj.length; i++) {
path.push(`${parentKey}[${i}]`);
const result = keyExists(obj[i], key);
if (result) {
return result;
}
path.pop();
}
} else {
for (const k in obj) {
path.push(k);
const result = keyExists(obj[k], key);
if (result) {
return result;
}
path.pop();
}
}
return false;
};
keyExists(ob);
return path.join('.');
};
this is what im trying to avoid:
onEditDataCallback={(rowData, columnId, newValue) => {
const temporary = rowData;
switch (columnId) {
case 'city': {
temporary.address.city.name = newValue;
break;
}
case 'addressOne': {
temporary.address.addressOne = newValue;
break;
}
case 'postalCode': {
temporary.address.postalCode.key = newValue;
break;
}
default: {
temporary[columnId] = newValue;
}
}
return {
...temporary
};
}}

Related

A way to convert an object with keys of . seperated strings into a JSON object

I'm trying to figure out a way to turn and object like this :
{ "test.subtest.pass" : "test passed", "test.subtest.fail" : "test failed" }
into JSON like this:
{ "test": { "subtest": { "pass": "test passed", "fail": "test failed" }}}
sometimes there may be duplicate keys, as above perhaps there would be another entry like "test.subtest.pass.mark"
I have tried using the following method and it works but it's incredibly ugly:
convertToJSONFormat() {
const objectToTranslate = require('<linkToFile>');
const resultMap = this.objectMap(objectToTranslate, (item: string) => item.split('.'));
let newMap:any = {};
for (const [key,value] of Object.entries(resultMap)) {
let previousValue = null;
// #ts-ignore
for (const item of value) {
// #ts-ignore
if (value.length === 1) {
if(!newMap.hasOwnProperty(item)) {
newMap[item] = key
} // #ts-ignore
} else if (item === value[value.length - 1]) {
if(typeof previousValue[item] === 'string' ) {
const newKey = previousValue[item].toLowerCase().replace(/\s/g, '');;
const newValue = previousValue[item];
previousValue[item] = {};
previousValue[item][newKey] = newValue;
previousValue[item][item] = key;
} else {
previousValue[item] = key;
}
} else if (previousValue === null) {
if (!newMap.hasOwnProperty(item)) {
newMap[item] = {};
}
previousValue = newMap[item];
} else {
if (!previousValue.hasOwnProperty(item)) {
previousValue[item] = {}
previousValue = previousValue[item];
} else if (typeof previousValue[item] === 'string') {
const newValue = previousValue[item];
previousValue[item] = {};
previousValue[item][item] = newValue;
} else {
previousValue = previousValue[item];
}
}
}
}
return newMap;
}
We can utilize recursion to make the code a little less verbose:
function convertToJSONFormat(objectToTranslate) {
// create root object for the conversion result
const result = {};
// iterate each key-value pair on the object to be converted
Object
.entries(objectToTranslate)
.forEach(([path, value]) => {
// utilize a recursive function to write the value into the result object
addArrayPathToObject(result, path.split("."), value);
});
return result;
}
function addArrayPathToObject(root, parts, value) {
const p = parts.shift();
// base-case: We attach the value if we reach the last path fragment
if (parts.length == 0) {
root[p] = value
return;
}
// general case: check if root[p] exists, otherwise create it and set as new root.
if(!root[p]) root[p] = {};
addArrayPathToObject(root[p], parts, value)
}
This function utilizes the fact that objects are pass-by-reference to recursively traverse through the object starting at its root until setting the desired value.
You can add error-handling and other such concerns as necessary for your use.
#Meggan Naude, toJson function copies json object to reference obj for provided keys and value.
const p = { "test.subtest.pass" : "test passed", "test.subtest.fail" : "test failed" };
const result = {} ;
const toJson = (obj, keys, value) => {
if (keys?.length === 1) {
obj[keys[0]] = value;
return obj
} else {
const k = keys.splice(0, 1)
if (k in obj) {
toJson(obj[k], keys, value)
} else {
obj[k] = {};
toJson(obj[k], keys, value)
}
return obj
}
}
Object.keys(p).forEach(key => toJson(result, key.split('.'), p[key]))
console.log(result);

es6 code broken in es5

I have been trying to translate my code from es6 to es5 because of some framework restrictions at my work... Although I have been quite struggling to locate what the problem is. For some reason the code does not work quite the same, and there is no errors either ...
Can someone tell me If I have translated properly ?
This is the ES6 code :
function filterFunction(items, filters, stringFields = ['Title', 'Description'], angular = false) {
// Filter by the keys of the filters parameter
const filterKeys = Object.keys(filters);
// Set up a mutable filtered object with items
let filtered;
// Angular doesn't like deep clones... *sigh*
if (angular) {
filtered = items;
} else {
filtered = _.cloneDeep(items);
}
// For each key in the supplied filters
for (let key of filterKeys) {
if (key !== 'TextInput') {
filtered = filtered.filter(item => {
// Make sure we have something to filter by...
if (filters[key].length !== 0) {
return _.intersection(filters[key], item[key]).length >= 1;
}
return true;
});
}
// If we're at TextInput, handle things differently
else if (key === 'TextInput') {
filtered = filtered.filter(item => {
let searchString = "";
// For each field specified in the strings array, build a string to search through
for (let field of stringFields) {
// Handle arrays differently
if (!Array.isArray(item[field])) {
searchString += `${item[field]} `.toLowerCase();
} else {
searchString += item[field].join(' ').toLowerCase();
}
}
// Return the item if the string matches our input
return searchString.indexOf(filters[key].toLowerCase()) !== -1;
});
}
}
return filtered;
}
And this is the code I translated that partially 99% work ..
function filterFunction(items, filters, stringFields, angular) {
// Filter by the keys of the filters parameter
var filterKeys = Object.keys(filters);
// Set up a mutable filtered object with items
var filtered;
// Angular doesn't like deep clones... *sigh*
if (angular) {
filtered = items;
} else {
filtered = _.cloneDeep(items);
}
// For each key in the supplied filters
for (var key = 0 ; key < filterKeys.length ; key ++) {
if (filterKeys[key] !== 'TextInput') {
filtered = filtered.filter( function(item) {
// Make sure we have something to filter by...
if (filters[filterKeys[key]].length !== 0) {
return _.intersection(filters[filterKeys[key]], item[filterKeys[key]]).length >= 1;
}
return true;
});
}
// If we're at TextInput, handle things differently
else if (filterKeys[key] === 'TextInput') {
filtered = filtered.filter(function(item) {
var searchString = "";
// For each field specified in the strings array, build a string to search through
for (var field = 0; field < stringFields.length; field ++) {
// Handle arrays differently
console.log(field);
if (!Array.isArray(item[stringFields[field]])) {
searchString += item[stringFields[field]] + ' '.toLowerCase();
} else {
searchString += item[stringFields[field]].join(' ').toLowerCase();
}
}
// Return the item if the string matches our input
return searchString.indexOf(filters[filterKeys[key]].toLowerCase()) !== -1;
});
}
}
return filtered;
}
These two lines
searchString += `${item[field]} `.toLowerCase();
searchString += item[stringFields[field]] + ' '.toLowerCase();
are not equivalent indeed. To apply the toLowerCase method on all parts of the string, you'll need to wrap the ES5 concatenation in parenthesis:
searchString += (item[stringFields[field]] + ' ').toLowerCase();
or, as blanks cannot be lowercased anyway, just use
searchString += item[stringFields[field]].toLowerCase() + ' ';
Here is a translated code from babeljs itself, as commented above.
'use strict';
function filterFunction(items, filters) {
var stringFields = arguments.length <= 2 || arguments[2] === undefined ? ['Title', 'Description'] : arguments[2];
var angular = arguments.length <= 3 || arguments[3] === undefined ? false : arguments[3];
// Filter by the keys of the filters parameter
var filterKeys = Object.keys(filters);
// Set up a mutable filtered object with items
var filtered = void 0;
// Angular doesn't like deep clones... *sigh*
if (angular) {
filtered = items;
} else {
filtered = _.cloneDeep(items);
}
// For each key in the supplied filters
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
var _loop = function _loop() {
var key = _step.value;
if (key !== 'TextInput') {
filtered = filtered.filter(function (item) {
// Make sure we have something to filter by...
if (filters[key].length !== 0) {
return _.intersection(filters[key], item[key]).length >= 1;
}
return true;
});
}
// If we're at TextInput, handle things differently
else if (key === 'TextInput') {
filtered = filtered.filter(function (item) {
var searchString = "";
// For each field specified in the strings array, build a string to search through
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
try {
for (var _iterator2 = stringFields[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var field = _step2.value;
// Handle arrays differently
if (!Array.isArray(item[field])) {
searchString += (item[field] + ' ').toLowerCase();
} else {
searchString += item[field].join(' ').toLowerCase();
}
}
// Return the item if the string matches our input
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (!_iteratorNormalCompletion2 && _iterator2.return) {
_iterator2.return();
}
} finally {
if (_didIteratorError2) {
throw _iteratorError2;
}
}
}
return searchString.indexOf(filters[key].toLowerCase()) !== -1;
});
}
};
for (var _iterator = filterKeys[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
_loop();
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
return filtered;
}
p.s. Or there is a better way to use babeljs directly without manually converting it.

Javascript: deep find object in tree, then return object and the path to it through the tree

I've got a data structure that looks something like this:
let tree = {
id: 1,
name: "Some Name",
children: [
{
id: 2,
name: "Child 1",
children: [...more nested objects...]
}
]
};
I've written a recursive function to find a given object within that tree, but I now need to also return the path through the tree to the object that is returned. I'm trying to figure out how to modify my search function to do this.
Search function:
_findInTree = (id, tree) => {
let result;
if (tree.id === id) {
result = tree;
} else {
for (let child of tree.children) {
if (child.id === id) { result = child; }
result = this._findInTree(id, child);
if (result) { break; }
}
}
return result;
}
You'll need the array index, so you can either track it outside the for-of and then use it on the path, or use Array#some instead (or use a boring old for).
Here's tracking the index outside the for-of — I also added an else I think was probably pretty important: :-)
_findInTree = (id, tree, path = "") => {
let result;
let index;
let rv;
if (tree.id === id) {
result = tree;
} else {
index = 0;
for (let child of tree.children) {
if (child.id === id) {
result = child;
break;
}
rv = this._findInTree(id, child, path + "[" + index + "]");
if (rv != null) {
return rv;
}
++index;
}
}
return { result, path };
};
Obviously, adjust the format of path as you see fit. (Doesn't have to be a string, for instance, could be an array.)
Here's the some solution:
_findInTree = (id, tree, path = "") => {
let result;
let rv = null;
if (tree.id === id) {
result = tree;
} else {
tree.children.some((child, index) => {
if (child.id === id) {
result = child;
return true;
}
rv = this._findInTree(id, child, path + "[" + index + "]");
if (rv) {
return true;
}
});
}
return rv || { result, path };
};
So T.J. Crowders position ended up having a bug around recording the path, and I ended up tweaking the solution to get the following, which works excellently.
_findInTree(id, tree) {
if (tree.id === id) {
let path = [tree.name];
return {result: tree, path};
} else {
for (let child of tree.children) {
let tmp = this._findInTree(id, child);
if (!_.isEmpty(tmp)) {
tmp.path.unshift(tree.name);
return tmp;
}
}
return {};
}
}
As For me, I need to change Kevin Whitaker code to this one
_findInTree(id, tree) {
if (tree.id === id) {
let path = [tree.name];
return {result: tree, path};
} else if (tree.children) { //THIS IS THE CHANGES THAT I NEED
for (let child of tree.children) {
let tmp = this._findInTree(id, child);
if (!_.isEmpty(tmp)) {
tmp.path.unshift(tree.name);
return tmp;
}
}
return {};
}
}

Typescript Dictionary Coding of Effective Javascript dict function into class

What would be a good Typescript class derived from the example in "Effective Javascript" dict example
function Dict(elements) {
// allow an optional initial table
this.elements = elements || {}; // simple Object
this.hasSpecialProto = false; // has "__proto__" key?
this.specialProto = undefined; // "__proto__" element
}
Dict.prototype.has = function (key) {
if (key === "__proto__") {
return this.hasSpecialProto;
}
// own property only
return {}.hasOwnProperty.call(this.elements, key);
};
Dict.prototype.get = function (key) {
if (key === "__proto__") {
return this.specialProto;
}
// own property only
return this.has(key)
? this.elements[key]
: undefined;
};
Dict.prototype.set = function (key, val) {
if (key === "__proto__") {
this.hasSpecialProto = true;
this.specialProto = val;
} else {
this.elements[key] = val;
}
};
Dict.prototype.remove = function (key) {
if (key === "__proto__") {
this.hasSpecialProto = false;
this.specialProto = undefined;
} else {
delete this.elements[key];
}
};
The bot wants me to enter more explanation which I would regard as verbosity. Who knows when I will type enough to please it. It also wants me to indent things perfectly, too.
Assuming your original code is correct. The following is the code transformed to TypeScript:
class Dict {
hasSpecialProto = false; // has "__proto__" key?
specialProto = undefined; // "__proto__" element
constructor(public elements = {}) {
// allow an optional initial table
}
has(key) {
if (key === "__proto__") {
return this.hasSpecialProto;
}
// own property only
return {}.hasOwnProperty.call(this.elements, key);
}
get(key) {
if (key === "__proto__") {
return this.specialProto;
}
// own property only
return this.has(key)
? this.elements[key]
: undefined;
}
set(key, val) {
if (key === "__proto__") {
this.hasSpecialProto = true;
this.specialProto = val;
} else {
this.elements[key] = val;
}
}
remove(key) {
if (key === "__proto__") {
this.hasSpecialProto = false;
this.specialProto = undefined;
} else {
delete this.elements[key];
}
}
}
Note that TypeScript will know what this means in the functions + I used a default parameter in the constructor.
If you are looking for something more powerful (and tested) with generics take a look at TypeScript collections : https://github.com/basarat/typescript-collections#included-data-structures

How to declare array of specific type in javascript

Is it possible in java script to explicitly declare array to be an array of int(or any other type)?
something like var arr: Array(int) would be nice...
var StronglyTypedArray=function(){
this.values=[];
this.push=function(value){
if(value===0||parseInt(value)>0) this.values.push(value);
else return;//throw exception
};
this.get=function(index){
return this.values[index]
}
}
EDITS: use this as follows
var numbers=new StronglyTypedArray();
numbers.push(0);
numbers.push(2);
numbers.push(4);
numbers.push(6);
numbers.push(8);
alert(numbers.get(3)); //alerts 6
Array of specific type in typescript
export class RegisterFormComponent
{
genders = new Array<GenderType>();
loadGenders()
{
this.genders.push({name: "Male",isoCode: 1});
this.genders.push({name: "FeMale",isoCode: 2});
}
}
type GenderType = { name: string, isoCode: number }; // Specified format
If simply you want to restrict user to push values as per first value entered you can use below code
var stronglyTypedArray = function(type) {
this.values = [];
this.typeofValue;
this.push = function(value) {
if(this.values.length === 0) {
this.typeofValue = typeof value;
this.pushValue(value);
return;
}
if(this.typeofValue === typeof value) {
this.pushValue(value);
} else {
alert(`type of value should be ${this.typeofValue}`)
}
}
this.pushValue = function(value) {
this.values.push(value);
}
}
If you want to pass your own type, you can customize the above code a bit to this
var stronglyTypedArray = function(type) {
this.values = [];
this.push = function(value) {
if(type === typeof value) {
this.pushValue(value);
} else {
alert(`type of value should be ${type}`)
}
}
this.pushValue = function(value) {
this.values.push(value);
}
}

Categories