Given this example using lodash:
var object = {};
_.set(object, [ 1, 2 ], 5);
console.log(object);
The resultant object in the console is:
{
1: [undefined, undefined, 5]
}
Now imagine, instead of the integer 2, you are setting a timestamp of 1445231475. You now have 1445231474 undefined values in a very large array that will run out of memory after a few _.set operations.
If possible with _.set, how would one go about creating this object:
{
1: {
2: 5
}
}
It is possible if 2 is truly a string like "a", but Lodash will force even "2" into the array of multiple undefined values.
I can use _.merge if necessary, but I would be more excited about using _.set capability.
you can do this with lodash setWith
// unwanted behavior with set
let x = {}
_.set(x, [1,2], 'test')
// x = { '1': [ <2 empty items>, 'test' ] }
// desired behavior with setWith
let y = {}
_.setWith(y, [1,2], 'test', Object)
// y = { '1': { '2': 'test' } }
Apparently, no, it's not possible to do that, and here's why:
Within _.set, this is the relevant part:
if (index == lastIndex) {
nested[key] = value;
} else if (nested[key] == null) {
nested[key] = isIndex(path[index + 1]) ? [] : {};
}
What happens here is that whenever a path item doesn't exist as a key, it is tested if it is a valid index (is of type number, or matches /^\d+$/) and if so an array is created, otherwise an object.
Because key is a string, and the goal is to set a property named 2 (or any other numerical value), it will always return true from isIndex and will results in an array.
Related
I have a react update form and i wish to differentiate the form data and the current data to figure out what has changed dynamically.
Problem summarized
find the minimum differences between 2 nested objects. And output an array of changed properties paths. E.g. if department_id in the departments list at index 0 changes while the rest stays the same - the algorithm should output ['departments'].
Example Data:
My data usually comes in like this (This is a simplified form , but the data has varying depths as shown here):
{id:115,
departments: [{
department_id:1,
department_name:"asd"}],
name: 'Test project',
}
Lets say the user decides to add in a department to the object, i wish to be able to detect the changes this way:
changes = ['departments']
or if the user changes the name :
changes = ['name']
the additional challenge here is that i wish to use this function across my forms , which means that the comparing should be able to handle different keys and depths of data
Edit:
data1 :
creation_date: "2020-06-16"
customer_information: Array(1)
0: 1
project_status: 1
sales_department: 1
sales_project_name: "helloss2sasdssssssssssss"
userProfile: Array(2)
0: 1
data2:
creation_date: "2020-06-16"
customer_information: Array(1)
0: 1
project_status: 1
sales_department: 1
sales_project_name: "helloss2"
userProfile: Array(2)
0: 1
1: 2
Function called here :
const data1 = action.original
const data2 = action.final
const difference = Object.keys(data1).filter((key)=>!walk(data1[key],data2[key]))
console.log(difference)
Here is the console log for difference :
[]
Expected:
['userProfile' , 'sales_project_name']
Simple naive recursive function walk that deep equals and returns if the branch has changes. filters keys that match.
data1 = {
creation_date: "2020-06-16",
customer_information: [1],
project_status: 1,
sales_department: 1,
sales_project_name: "helloss2sasdssssssssssss",
userProfile: [1],
version: 1
}
data2 = {
creation_date: "2020-06-16",
customer_information: [1],
project_status: 1,
sales_department: 1,
sales_project_name: "helloss2",
userProfile: [1, 2],
version: 2
}
walk = (node1, node2) => {
// different types, return false
if (typeof node1 !== typeof node2) return false
if (node1 && node2 && typeof node1 === 'object') {
const keys = Object.keys(node1)
// if type object, check same number of keys and walk on node1, node2
return keys.length === Object.keys(node2).length &&
keys.every(k => walk(node1[k], node2[k]))
}
// not object and types are same, return if node1 is equal to node2
return node1 === node2
}
console.log(
Object.keys(data1).filter((key) => !walk(data1[key], data2[key]))
)
If you don't want to use any library for comparing nested objects, you could simply convert to JSON and compare the strings.
Assuming you want to compare objects, it could look like this:
function getUnequalKeys(object1, object2) {
let unequalKeys = [];
for (let key in object1) {
if (object1.hasOwnProperty(key)
&& (
!object2.hasOwnProperty(key)
|| JSON.stringify(object1[key]) !== JSON.stringify(object2[key])
)
) {
unequalKeys.push(key);
}
}
for (let key2 in object2) {
if (object2.hasOwnProperty(key2) && !object1.hasOwnProperty(key2)) {
unequalKeys.push(key2);
}
}
return unequalKeys;
}
This would return all first-level keys that don't exist in both objects or have different values.
EDIT:
What it essentially does is the following:
Loop through each key in object1. Check if that same key exists in object2. If it does not exist, it means the key is not equal, so the condition is true and the key is added to the list of unequal keys. If the key does exist in object2, make a JSON string of both values and compare those strings. If the strings are not the same, it means they have different values. In that case, also add the key to the array of unequal keys.
Now we already checked all keys of object1.
As a last step, go through all keys of object2 and check if they are not present in object1, and in that case also add them to the array of unequal keys.
Here is my example. I have an object in Javascript that is dynamically changed.
And I want in this case the value of the property "inside" and I know that the value is in the place a[something][anotherthing][inside]. Because I saved the position in an array ["a","somethig", "anotherthing"]. my question is How do I move to that position using the keys that are in the array?. Already tried to concat the elements and the final result is something like this myObject[a][somthing][anotherthing] but the problem is that it returns 'undefined' because it's a string. Is there any chance to convert it to object or some way to get that position in the object?
var myarray = ['a', 'something', 'anotherthing'];
myObject = {
a: {
something: {
anotherthing: {
inside: 10
}
}
},
b: {
insideb: {}
}
}
Use reduce to reduce the array to a single value. You will pass in myObject as the starting point (second parameter), then use this basic callback (first parameter):
(obj, itm) => obj[itm]
When you put it all together, it will look like this:
var myarray = ['a', 'something', 'anotherthing'];
myObject = {
a: {
something: {
anotherthing: {
inside: 10
}
}
},
b: {
insideb: {
}
}
}
let result = myarray.reduce((obj, itm) => obj[itm], myObject)
console.log(result)
console.log(result.inside)
If you know the exact location of the value: myObject['a']['somthing']['anotherthing'] will give you the value.
If you need to step through the object dynamically, you can use: Object.keys(myObject).forEach(key => myObject[key]); to get the top level keys.
Hi I am having an object like this, I want to return the property values as undefined or null if not present in the object. Here is my object.
var build= { 'ts1': 1, 'ts2': '2', 'ts3': 3 };
I am using _.pick function to pick the values it only pick values that are present in the object.
Here is my code:
_.pick(build, [ 'ts0' 'ts1', 'ts2','ts5','ts6','ts7']);
I am getting the result like this:
{ 'ts1': 1, 'ts2': '2'}
and the expected output is:
{ 'ts0':null, 'ts1': 1, 'ts2': '2','ts5':null,'ts6':null,'ts7':null}
Please can anybody help me on this.
_.pick is designed to give you a subset of the properties of an object. It will not assign a property to null that had never existed in the original object.
However, you mentioned that:
I want to return the property values as undefined or null if not present in the object
Luckily for you, JavaScript already does this by default (returning undefined) for all property accesses where the property does not exist on the target object. So, you could simply use the code you have already and use regular bracket property access notation to have property values default to undefined:
var build = {
'ts1': 1,
'ts2': '2',
'ts3': 3
}
var result = _.pick(build, [
'ts0', 'ts1', 'ts2', 'ts5', 'ts6', 'ts7'
])
console.log(result['ts0']) //=> undefined
console.log(result['ts1']) //=> 1
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
Edit: If you really need the properties to exist on the object and default to null, you can use the following method:
var build = {
'ts1': 1,
'ts2': '2',
'ts3': 3
}
var result = _.reduce([
'ts0', 'ts1', 'ts2', 'ts5', 'ts6', 'ts7'
], function (o, e) {
o[e] = e in build ? build[e] : null
return o
}, {})
console.log(result['ts0']) //=> undefined
console.log(result['ts1']) //=> 1
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
You can use for..of loop to iterate array, assign properties to object, set value to value of build at that property, or null
var build = { 'ts1': 1, 'ts2': '2', 'ts3': 3 };
let props = ['ts0', 'ts1', 'ts2','ts5','ts6','ts7'];
let res = {};
for (let prop of props) res[prop] = build[prop] || null;
console.log(res);
You can iterate through the desired values and build a new object. _.set will add the value to the new object. _.get will attempt to get the value from the existing object, if it does not exist then it will use null as the default value.
var build = {
'ts1': 1,
'ts2': '2',
'ts3': 3
};
var pickValues = ['ts0', 'ts1', 'ts2', 'ts5', 'ts6', 'ts7'];
var result = {};
_.forEach(pickValues, function(key) {
_.set(result, key, _.get(build, key, null));
});
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
That's just not how _.pick works. You could use _.assign to accomplish what you are trying to do.
var build = {
'ts1': 1,
'ts2': '2',
'ts3': 3
};
let result = _.assign({
ts0: null,
ts1: null,
ts2: null,
ts3: null,
ts4: null,
ts5: null,
ts6: null,
ts7: null
},
build);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
I am using _.isEqual that compares 2 array of objects (ex:10 properties each object), and it is working fine.
Now there are 2 properties (creation and deletion) that i need not to be a part of comparison.
Example:
var obj1 = {name: "James", age: 17, creation: "13-02-2016", deletion: "13-04-2016"}
var obj2 = {name: "Maria", age: 17, creation: "13-02-2016", deletion: "13-04-2016"}
// lodash method...
_.isEqual(firstArray, secondArray)
You can use omit() to remove specific properties in an object.
var result = _.isEqual(
_.omit(obj1, ['creation', 'deletion']),
_.omit(obj2, ['creation', 'deletion'])
);
var obj1 = {
name: "James",
age: 17,
creation: "13-02-2016",
deletion: "13-04-2016"
};
var obj2 = {
name: "Maria",
age: 17,
creation: "13-02-2016",
deletion: "13-04-2016"
};
var result = _.isEqual(
_.omit(obj1, ['creation', 'deletion']),
_.omit(obj2, ['creation', 'deletion'])
);
console.log(result);
<script src="https://cdn.jsdelivr.net/lodash/4.13.1/lodash.min.js"></script>
#ryeballar's answer is not great for large objects because you are creating a deep copy of each object every time you do the comparison.
It's better to use isEqualWith. For example, to ignore differences in the "creation" and "deletion" properties:
var result = _.isEqualWith(obj1, obj2, (value1, value2, key) => {
return key === "creation" || key === "deletion" ? true : undefined;
});
EDIT (important caveat pointed out in the comments): if objects have different numbers of keys, then isEqualWith considers them to be different, regadless of what your customizer does. Therefore do not use this approach if you want to ignore an optional property. Instead, consider using _.isMatch(), _.isMatchWith(), or #ryeballar's _.omit() approach.
Note that if you're writing for ES5 and earlier, you'll have to replace the arrow syntax (() => {) with function syntax (function() {)
_.omit creates deep copy of the object. If you need to exclude only root props it is better to create shallow copy using, for example, destructuring assignment:
const x = { a: 4, b: [1, 2], c: 'foo' }
const y = { a: 4, b: [1, 2], c: 'bar' }
const { c: xC, ...xWithoutC } = x
const { c: yC, ...yWithoutC } = y
_.isEqual(xWithoutC, yWithoutC) // true
xWithoutC.b === x.b // true, would be false if you use _.omit
Best way is not to create copies at all (TypeScript):
function deepEqual(
x?: object | null,
y?: object | null,
ignoreRootProps?: Set<string>
) {
if (x == null || y == null) return x === y
const keys = Object.keys(x)
if (!_.isEqual(keys, Object.keys(y)) return false
for (let key of keys) {
if (ignoreRootProps && ignoreRootProps.has(key)) continue
if (!_.isEqual(x[key], y[key])) return false
}
return true
}
You could map your array into a "cleaned" array, then compare those.
// Create a function, to do some cleaning of the objects.
var clean = function(obj) {
return {name: obj.name, age: obj.age};
};
// Create two new arrays, which are mapped, 'cleaned' copies of the original arrays.
var array1 = firstArray.map(clean);
var array2 = secondArray.map(clean);
// Compare the new arrays.
_.isEqual(array1, array2);
This has the downside that the clean function will need to be updated if the objects are expecting any new properties. It is possible to edit it so that it removes the two unwanted properties instead.
I see two options.
1) Make a second copy of each object that doesn't contain the creation or date.
2) Loop through all the properties and, assuming you know for certain that they both have the same properties, try something like this.
var x ={}
var y ={}
for (var property in x) {
if(property!="creation" || property!="deletion"){
if (x.hasOwnProperty(property)) {
compare(x[property], y[property])
}
}
}
Where compare() is some simple string or object comparison. If you are certain of the properties on one or both the objects, you can simplify this code a bit further, but this should work in most cases.
My final solution required a full comparison ignoring an optional property so the above solutions did not work.
I used a shallow clone to remove the keys I wanted to ignore from each object before comparing with isEqual:
const equalIgnoring = (newItems, originalItems) => newItems.length === originalItems.length
&& newItems.every((newItem, index) => {
const rest1 = { ...newItem };
delete rest1.creation;
delete rest1.deletion;
const rest2 = { ...originalItems[index] };
delete rest2.creation;
delete rest2.deletion;
return isEqual(rest1, rest2);
});
If you want to check a subset for each item in the array this works:
const equalIgnoringExtraKeys = (fullObjs, partialObjs) =>
fullObjs.length === partialObjs.length
&& fullObjs.every((fullObj, index) => isMatch(fullObj, partialObjs[index]));
If you also want to ignore a specific property and check subset:
const subsetIgnoringKeys = (fullObjs, partialObjs) =>
fullObjs.length === partialObjs.length
&& fullObjs.every((fullObj, index) => isMatchWith(
fullObj,
partialObjs[index],
(objValue, srcValue, key, object, source) => {
if (["creation", "deletion"].includes(key)) {
return true;
}
return undefined;
}
));
I am trying to implement a Trie in Javascript, which is easy enough but I seem to have hit a road block with my object.
The nodes are structured as follows:
var node = {
children: []
}
Children is an array of nodes that is mapped by a letter in a string. So the string "Test" would look like this:
root = {
children: [
't' => {
children: [
'e' => {
children: [
's' => {
children: [
't' => {
children: []
}
]
}
]
}
]
}
]
};
So each children array should have a length of 1, but if do something like alert(this._root.children.length); I get zero. Any thoughts on why this is happening?
Here is the rest of my implementation:
function Trie() {
this._root = {
children: []
};
}
Trie.prototype = {
//restore constructor
constructor: Trie,
add: function (str){
var curr = this._root,
prev,
currchar;
// For each character in the string
for(var i = 0, j = str.length; i < j; i++) {
// Insert only lowercase letters for efficiency
currchar = str.toLowerCase().charAt(i);
prev = curr;
curr = prev.children[currchar];
// Traverse until we hit a non-existant node
if(typeof(curr) == "undefined") {
// Make a new node
prev.children[currchar] = {
children: []
};
curr = prev.children[currchar];
}
}
}
You are adding properties to the array instance object, not elements to the array. The length property only includes array elements, not properties on the array instance object.
var a = [23, 42];
console.log(a.length); // 2
a['foo'] = 'bar';
console.log(a.length); // 2
a[2] = 1337;
console.log(a.length); // 3
EDITED:
You could instead structure the nodes like this:
var node = {
children: {},
length: function () {
var i = 0;
var k;
for (k in this.children) {
if (this.children.hasOwnProperty(k)) {
i++;
}
}
return i;
}
};
This is inefficient, of course. You should instead define a Node class with the length method on its prototype. Alternatively, define an add method that updates the length property.
I think that the problem is that you use a javasrcipt array as an associative array (as found in other languages). In javascript "associative" arrays are objects that don't have a length property. Normal arrays have numeric indices.
Irrelevant to the question but you might find this useful.
Maybe you want
str.toLowerCase().charCodeAt(i)
instead of
str.toLowerCase().charAt(i)
If str is "f1", the properties you're adding to the children array are "f" and "1" which should cause an array with property named f and length 0, and another child array with length 2 and property 1.
To get only numeric properties, you should make sure your property names are valid array indices -- positive integers representable in 31 bits.
By using charCodeAt instead of charCode, you would get the property names 102 and 49 instead of "f" and 1.