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.
Related
Sorry for misleading title here, I wasn't able to frame any proper one.
I am confused in array when there is nothing inside them (prints by empty X n) but they have length.
e.g. I create array by const a = [,,,]. This creates an array whose length is 3 but nothing inside it. If i print it in browser console, it prints the following:
What does empty mean here? If I run map or forEach function and try to console something, I get nothing.
Have added some code.
const a = [,,,]
console.log("print a: ",a)
console.log("print a.length: ",a.length)
console.log("print typeof a[0]: ", typeof a[0])
console.log("a.forEach((data, index) => { console.log(data, index) }): ", a.forEach((data, index) => { console.log(data, index) }))
console.log("")
const b = [undefined, undefined, undefined]
console.log("print b: ", b)
console.log("print b.length: ", b.length)
console.log("print typeof b[0]: ", typeof b[0])
console.log("b.forEach((data, index) => { console.log(data, index) }): ", b.forEach((data, index) => { console.log(data, index) }))
console.log("")
console.log("compare a[0] and b[0]: ", a[0] === b[0])
The only thing which differs is when I print a and b (though stackoverflow console prints them same but browser console prints differently) and when I try to loop through the array. Also momentjs isEqual gives them equal (jsfiddle here)
My main doubts are:
What type of array is it?
What does empty mean here?
How is it different from array which has all undefined values or empty array? or is it not?
Do we use it or any sample use case for this one
I have read about null and undefined array values and have understood it. But for this one, I haven't find anything proper. Most of the search I found were related to const a = [] is an empty array or how to check if array is empty and so on.
So, if someone can explain or give any proper links to read, it will be very helpful.
Please let me know, if I should add anything else.
Intro to sparse arrays
First a clarification what you've created is called a sparse array. To put it simply, sparse arrays are similar to normal arrays but not all of their indexes have data. In some cases, like JavaScript, this leads to slightly more significant handling of them. Other languages simply have a normal array of fixed length with some values that are "zero" in some sense (depends on what value can signify "nothing" for a specific array - might be 0 or null or "", etc).
Empty slots
The empty slot in a sparse array is exactly what it sounds like - slot that is not filled with data. JavaScript arrays unlike most other implementations, are not fixed size and can even have some indexes simply missing. For example:
const arr = []; // empty array
arr[0] = "hello"; // index 0 filled
arr[2] = "world"; // index 2 filled
You will get an array with no index 1. It's not null, nor it's empty, it's not there. This is the same behaviour you get when you have an object without a property:
const person = {foo: "hello"};
You have an object with a property foo but it doesn't have, for example, a bar property. Exactly the same as how the array before doesn't have index 1.
The only way JavaScript represents a "value not found" is with undefined, however that conflates
"the property exists and the value assigned to it is undefined"
"the property does not exist at all"
Here as an example:
const person1 = { name: "Alice", age: undefined };
const person2 = { name: "Bob" };
console.log("person1.age", person1.age);
console.log("person2.age", person2.age);
console.log("person1.hasOwnProperty('age')", person1.hasOwnProperty('age'));
console.log("person2.hasOwnProperty('age')", person2.hasOwnProperty('age'));
You get undefined when trying to resolve age in either case, however the reasons are different.
Since arrays in JavaScript are objects, you get the same behaviour:
const arr = []; // empty array
arr[0] = "hello"; // index 0 filled
arr[2] = "world"; // index 2 filled
console.log("arr[1]", arr[1]);
console.log("arr.hasOwnProperty(1)", arr.hasOwnProperty(1));
Why it matters
Sparse arrays get a different treatment in JavaScript. Namely, array methods that iterate the collection of items will only go through the filled slots and would omit the empty slots. Here is an example:
const sparseArray = []; // empty array
sparseArray[0] = "hello"; // index 0 filled
sparseArray[2] = "world"; // index 2 filled
const arr1 = sparseArray.map(word => word.toUpperCase());
console.log(arr1); //["HELLO", empty, "WORLD"]
const denseArray = []; // empty array
denseArray[0] = "hello"; // index 0 filled
denseArray[1] = undefined; // index 1 filled
denseArray[2] = "world"; // index 2 filled
const arr2 = denseArray.map(word => word.toUpperCase()); //error
console.log(arr2);
As you can see, iterating a sparse array is fine, but if you have an explicit undefined, in the array, then word => word.toUpperCase() will fail because word is undefined.
Sparse arrays are useful if you have numerically indexed data that you want to run .filter, .find, .map, .forEach and so on. Let's illustrate again:
//some collection of records indexed by ID
const people = [];
people[17] = { id: 17, name: "Alice", job: "accountant" , hasPet: true };
people[67] = { id: 67, name: "Bob" , job: "bank teller", hasPet: false };
people[3] = { id: 3 , name: "Carol", job: "clerk" , hasPet: false };
people[31] = { id: 31, name: "Dave" , job: "developer" , hasPet: true };
/* some code that fetches records */
const userChoice = 31;
console.log(people[userChoice]);
/* some code that transforms records */
people
.map(person => `Hi, I am ${person.name} and I am a ${person.job}.`)
.forEach(introduction => console.log(introduction));
/* different code that works with records */
const petOwners = people
.filter(person => person.hasPet)
.map(person => person.name);
console.log("Current pet owners:", petOwners)
its just what it is empty its neither undefined or null
const a = [,,,,] is same as const a = new Array(4)
here a is an array with no elements populated and with only length property
do this, let arr1 = new array() and then console.log(arr1.length) you'll get 0 as output. and if you do console.log(arr1) you'll get [ <4 empty items> ]
if you change the length property of arr1 like this arr1.length = 4 you will have an empty array with it's length property = 4, but no items are populated so those slot will be empty and if you do console.log(typeof(arr1[0]) you get undefined only because there is no other possible types to show. And no methods of Array will be applied with an array with empty elements
so,
Empty array means an array with length property and with unpopulated slots
this is different from arrays with undefined because in JS undefined is a type and you can execute and have results by calling all array methods on it, whereas an array with empty elememts have no type and no array methods can be applied on it.
I need to achieve the same output but as you see the length of the ID's array is zero because I cannot achieve this output using push command, it generates errors like:
push is not a function
Cannot use indexOf for undefined or false
I need to solve this array with a push command and make the output exactly like below but I cannot use the each function because the length is zero.
var RepeaterClass = {
Repeaters: [],
collectRepeaterValues: function (rep_id, cat_id, element_id) {
this.Repeaters[rep_id] = this.Repeaters[rep_id] || [];
this.Repeaters[rep_id][cat_id] = this.Repeaters[rep_id][cat_id] || [];
if (-1 === this.Repeaters[rep_id][cat_id].indexOf(element_id)) {
this.Repeaters[rep_id][cat_id].push(element_id);
}
},
};
Implementing of this code:
ID_1: Array(0)
category: Array(1)
0: "dog"
animals: Array(2)
0: "dog"
1: "cat"
As others have commented it's not entirely clear what you're asking here. This code sort of works if you fix the line var Repeaters =[];
I think the confusion is arising because we create Repeaters as an array, but then I think you must be calling collectRepeaterValues with strings for rep_id and cat_id (e.g. 'ID_1' and 'animals') to get the output you are showing. It should be called with numbers if you want to create arrays. You can't access an array element with a string.
If you call with strings JavaScript is going to create object properties on the array when you do Repeaters[rep_id] = Repeaters[rep_id] || []. That is to say, if we execute the statement Repeaters['ID_1'] = [] in JavaScript it's not doing array assignment even if Repeaters is an array. It will create an object property called ID_1 and makes its value the empty array.
The snippets below show calling the (corrected) object with numbers and with strings and the results.
As an aside, the if statement in collectRepeaterValues is not working.
Now we're back on what the question really is. Do you want arrays, which have to be indexed by numbers of course, or do you want objects with string properties?
// CALLING WITH STRINGS
var RepeaterClass = {
Repeaters: [], // Fixed so it's an object property
collectRepeaterValues: function (rep_id, cat_id, element_id) {
this.Repeaters[rep_id] = this.Repeaters[rep_id] || [];
this.Repeaters[rep_id][cat_id] = this.Repeaters[rep_id][cat_id] || [];
if (-1 === this.Repeaters[rep_id][cat_id].indexOf(element_id)) {
this.Repeaters[rep_id][cat_id].push(element_id);
}
}
}
// What I think you're doing?
RepeaterClass.collectRepeaterValues("ID_1", "category", "dog");
RepeaterClass.collectRepeaterValues("ID_1", "animals", "dog");
RepeaterClass.collectRepeaterValues("ID_1", "animals", "cat");
// At the top level RepeaterClass.Repeaters is an empty array with a 'ID_1' property
// Array length is zero...
console.log(RepeaterClass.Repeaters.length); // 0
// But we have a property ID_1, that is itself an array of zero length with category
// and animals properties that are arrays
console.log(RepeaterClass.Repeaters.ID_1.category[0]); // dog
console.log(RepeaterClass.Repeaters.ID_1.animals[0]); // dog
console.log(RepeaterClass.Repeaters.ID_1.animals[1]); // cat
// Note that this IS the result at the end of the question
// EDIT: You can iterate over the properties with for..in
console.log('Iterating categories on ID_1:');
for (var cat_id in RepeaterClass.Repeaters.ID_1) {
console.log(cat_id);
}
// CALLING WITH NUMBERS
var RepeaterClass = {
Repeaters: [], // Fixed so it's an object property
collectRepeaterValues: function (rep_id, cat_id, element_id) {
this.Repeaters[rep_id] = this.Repeaters[rep_id] || [];
this.Repeaters[rep_id][cat_id] = this.Repeaters[rep_id][cat_id] || [];
if (-1 === this.Repeaters[rep_id][cat_id].indexOf(element_id)) {
this.Repeaters[rep_id][cat_id].push(element_id);
}
}
}
// How this code is meant to be called I think
RepeaterClass.collectRepeaterValues(0, 0, "dog");
RepeaterClass.collectRepeaterValues(0, 1, "dog");
RepeaterClass.collectRepeaterValues(0, 1, "cat");
// At the top level RepeaterClass.Repeaters is now an array structure
console.log(RepeaterClass.Repeaters.length); // 1
console.log(RepeaterClass.Repeaters[0][0][0]); // dog
console.log(RepeaterClass.Repeaters[0][1][0]); // dog
console.log(RepeaterClass.Repeaters[0][1][1]); // cat
The Only Solution I found is by create another array to store the elements inside the repeater then push it in the main Repeaters array outside the funtion
But Still cannot achieve it in the same function.
var RepeaterClass = {
Repeaters: {},
validated: {},
collectRepeaterValues: function( cat_id, element_id ) {
// this.Repeaters[ rep_id ] = this.Repeaters[ rep_id ] || [];
this.validated[ cat_id ] = this.validated[ cat_id ] || [];
if ( -1 === this.validated[ cat_id ].indexOf( element_id ) ) {
this.validated[ cat_id ].push( element_id );
}
}
AnotherFunction: function() {
_.each( REP, function( repDetails, repID ) {
_.each( repDetails, function( value ) {
/* 1. Call the Collector */
collectRepeaterValues( value['cat'], value['id'] );
} );
/* 2. push the validated output inside the main array */
this.Repeaters[ repID ] = this.Repeaters[ repID ] || [];
this.Repeaters[ repID ] = this.validated;
/* Empty for another session */
this.validated = {};
} );
}
}
I have an object that I need to transform into an array. Here is the code I have already:
for (var key in categoryData[p]) { // categorydata is an object, the "p" is because this is taking place in a loop (array of objects)
if (categoryData[p].hasOwnProperty(key)) {
var objToArray = $.map(categoryData[p], function(value, key) {
return [value];
});
}
}
Right now, this is returning:
0 : value
1 : value
2 : value
I want it to return:
Key : Value
Key : Value
Key : Value
But I haven't found a way to do this with my data. Any direction would be greatly appreciated!
Edit: Adding more information:
I want to sort from the highest to lowest value. For clarification, I want the data to look like this:
(key) (object)
"ABC" : 8
"DEF" : 7
"GHI" : 5
I am putting it into an array to begin with because I can't sort the values when they're in an object (as far as I know).
My data is fairly complex, in a CSV file, but the idea of it is:
ABC, DEF, GHI
8 , 7 , 5
Associative arrays aren't a thing in javascript. You can either have arrays denoted by [] with 0 based numeric indices, or objects denoted by {} that can store key-value pairs. The latter construct can be used as replacement to associative arrays (ie add arbitrary keys and values to it), but they cannot be treated like arrays.
What you want in this case is what you already have - a key/value store, except it's called an object.
edit
If you just want to sort the data regardless of datatypes
You can split your object into multiple objects with a single key-value pair, then create an array from these objects and sort them any way you like using Array.sort(). Here's a quick example of splitting your provided data into objects:
var originalData = {
"ABC" : 8,
"DEF" : 7,
"GHI" : 5,
},
sortableArray = [];
for (key in originalData) {
sortableArray.push({
"key" : key,
"value" : originalData[key]
});
}
This creates a new object and appends it to our sortable [] array. To sort it according to its original value, you need to supply a comparator function that accesses the value property of the objects.
sortableArray.sort(function(a,b) {
return a.value - b.value;
});
This should return an array of objects ordered by the value property of each object in ascending order. Simply switch a and b around to get a descending order sort.
Hope this helps!
The best approach to sort your data is to map your object into an array to look like this:
[
{
"key": someKey
"value": someValue
},
{
"key": someOtherKey
"value": someOtherValue
},
//...
]
using this code:
var objToArray = $.map(categoryData[p], function(value, key) {
return {"key": key, "value": value};
});
And then sort it using this code:
objToArray.sort(function(a, b) {
// if the values are numbers (otherwise you have to change this to addapt to your dataType)
return b.value - a.value; // to sort from highest to lowest or a.value - b.value to sort from lowest to highest
});
And then you can use it like:
objToArray[0].key; // to get the key of the first item
objToArray[3].value; // to get the value of the 4-th item
// ...
You can loop through them as well (for(var i = 0; i < objToArray.length; i++)...).
In ES6, Object.entries(a).sort((a, b) => a[1] < b[1] )
This will give you something like this
[
["ABC", 8]
["DEF", 7]
["GHI", 5]
]
the .entries step gives you the list of pairs and the .sort step sorts them by their second value
I have and array which contains more then 10 000 values and somehow lodash method includes stopped working.
The example of array:
['888888111118888',
'7771117717717771']
And my method (not working) but were working yesterday as well (with lower amount of values in array)
toFind = '7771117717717771'; // this is just for example
return _.includes(arr, toFind);
and no matter if the toFind is in array the method returns "false". Any suggestions?
EDIT
so this is what I have noticed now:
console.log(data.memberList.members[0].steamID64[7]);
toFind = data.memberList.members[0].steamID64[7];
console.log(_.findIndex(data.memberList.members[0].steamID64, toFind));
and it responds with:
999999999999
-1
so how is this even possible?
Example of Array.prototype.findIndex:
let arr = [ "123", "456" ];
let index = arr.findIndex ( x => { return x === "123" } );
This will return 0 (the first index of "123"). If you have objects:
let arr = [ { val : "123" }, { val : "456" } ];
let index = arr.findIndex ( x => { return x.val === "123" } );
And so on. If you get -1, either your matcher is wrong, or it's not in the array.
I have tremendous respect for lodash, but as we get increasing native capability such as maps, sets, along with other functional higher order capabilities (filter, map, reduce, findIndex, etc), I've found my use of it diminishing.
Array.prototype.find
let result = arr.find(r => r ==='7771117717717771');
If result is defined then it exists in the array. If you are looking for an operation that runs faster than O(n) time, you should look into alternative data structures such as maps or trees
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.