Javascript appending object doesn't guarantee order - javascript

I have looked at this question in stack overflow about objects guaranteeing order.
Does JavaScript Guarantee Object Property Order?
some says they guarantee, some says they don't, depending on situations. Meantime I've encountered following problem.
I have an array of objects, similar to below:
const arrayObject = [
{id:'a123', bar:'hello'},
{id:'a321', bar: 'foo'} ];
now I wish to turn this arrayObject into object of object, with structure as follows with the same order as the array:
const object = {
'a123': {id:'a123', bar:'hello'},
'a321': {id:'a321', bar: 'foo'},
}
basically using the id of each item in the array as the key of the object. Below is the code I used to try to achieve it:
let newObj = {};
arrayObject.forEach(data=>{
const temp = {
[data.id]:{
id: data.id,
bar: data.bar
},
};
newObj={...newObj, ...temp};
})
I do get the correct structure, however the order is not the same as the order of arrayObject, i.e. it returns:
const object = {
'a321': {id:'a321', bar: 'foo'},
'a123': {id:'a123', bar:'hello'},
}
I've tried with more items in the array, and I get same result. It does not guarantee the order.
Is there something wrong with my code, or is it simply not guaranteeing the order?
What do I have to do to make the object be the same order as the array?

Preserve the order by including ordering information in the (unordered) object. Anytime later, when you need to recover the original order, use the saved ordering information...
const arrayObject = [{
id: 'a123',
bar: 'hello'
},
{
id: 'a321',
bar: 'foo'
}
];
let object = {}
arrayObject.forEach((e, i) => {
object[e.id] = { ...e, orderWith: i } // the index will tell us how to sort later
})
// later on
let sorted = Object.values(object).sort((a, b) => a.orderWith - b.orderWith)
console.log(sorted)

I think what you're looking for is a Map() object. See here -> Map and Set
const arrayObject = [
{id:'a123', bar:'hello'},
{id:'a321', bar: 'foo'},
{id:'a234', bar: 'more'},
{id:'a735', bar: 'words'},
{id:'a167', bar: 'added'},
{id:'a857', bar: 'now'},
];
var newObj = new Map();
for (var i=0; i<arrayObject.length; i++) {
const temp = {
id: arrayObject[i].id,
bar: arrayObject[i].bar
};
newObj.set(arrayObject[i].id, temp);
}
var jsonText = JSON.stringify(Array.from(newObj.entries()));
console.log(jsonText);
// To get at the elements, use .get()
console.log(newObj.get("a321").bar);

A simpler bit of code would be like this (use a for loop instead of forEach:
let newObj = {};
for (const data of arrayObject) {
newObj[data.id] = data;
}
This might get you what you want because it will guarantee that the order the object is built matches the order in the array. Using forEach causes multiple functions to be called in whatever order they run, which might be out of order. But realize that even using the for-loop does not guarantee the order will always match. An Array will guarantee the order, but the Object made this way does not. Even if the above code does give you the desired order, it might not in the future. Use an Array if you need to preserve order.

Related

Why is my element counter not properly functioning?

So I'm working on a project that compares different arrays. It's almost completely working, however I have one problem. Say that in this situation, a and b are two different fetches that hold array data. The data is mostly the same however one is slightly more updated than the other (the non-updated one is a subset essentially). The code I have down here is this:
async function dataDetect() {
let fetchA = await fetch('https://a/data');
let fetchB = await fetch('https://b/data');
let dataA = await fetchA.json();
let dataB = await fetchB.json();
let differenceDetector = (dataA, dataB) => {
let compareA = new Set(dataA);
for (const x of new Set(dataB)) {
if (compareA.has(x)) {
compareA.delete(x);
} else {
compareA.add(x);
}
}
return Array.from(compareA);
};
let detected = differenceDetector(dataA, dataB);
console.log('All differences script detected: ',{detected});
}
dataDetect();
And almost everything is working. However, I'm having a huge problem. For some reason whenever I run this, the total array is actually both of the arrays combined, and it never removed the common elements. I'm sure there is a way to fix this but I've tried multiple combinations as to how and none of them have fully worked. My problem is kinda like this(these aren't the actual arrays in my thing):
dataA=['Violet','Orange','Plumage','Crimson']
and
dataB=['Violet','Orange','Plumage','Crimson','Maroon'].
The console logs this: ['Violet','Orange','Plumage','Crimson','Violet','Orange','Plumage','Crimson','Maroon'].
My final log is literally just both of the arrays stacked. This works with normal arrays but with fetches it doesn't. Why does this happen and can someone explain how to fix this?
let differenceDetector = (dataA, dataB) => {
let compareA = new Set(dataA);
for (const x of new Set(dataB)) {
if (compareA.has(x)) {
compareA.delete(x);
} else {
compareA.add(x);
}
}
return Array.from(compareA);
};
const dataB = ['Violet', 'Orange', 'Plumage', 'Crimson', 'Maroon'];
const dataA = ['Violet', 'Orange', 'Plumage', 'Crimson'];
console.log(differenceDetector(dataA, dataB));
Also, I see some people wanted to know what the actual array data was.
I don't think you guys need to know the data but one guy said that if it was objects it wouldn't work. That's what the array is made of. Objects. So since there all objects how can I fix it?
The distinction between arrays of objects vs. strings has to do with how equality is tested. The Set has() method wraps an equality test (like ===) which works for immutable types but requires generalization to compare objects, such as those the OP might get from an API...
This intersection function allows the caller to pass in an arbitrary predicate. The caller can use it to perform an equivalence test on objects.
function differenceDetector(arrayA, arrayB, test) {
test = test || ((a, b) => a === b); // default to deep equality
return arrayA.filter(a => !arrayB.find(b => test(a,b)));
}
// this works as expected for strings...
const a = ['Violet', 'Orange', 'Plumage', 'Crimson', 'Maroon'];
const b = ['Violet', 'Orange', 'Plumage', 'Crimson'];
console.log(differenceDetector(a, b));
// and it also works for objects...
const c = [{ id: 1 }, { id: 2}, { id: 3 }, { id: 4 }];
const d = [{ id: 1 }, { id: 2}, { id: 3 }];
const test = (a, b) => a.id === b.id;
// note that we pass an equivalence predicate "test"
// without it, the default "===" test will give the OP's undesired result
console.log(differenceDetector(c, d, test));
Note that this implementation, like the OP's, is not commutative. The difference found is elements of the first array that are not in the second, according to a given predicate.
Also note, comparing whole objects for equivalence (all keys and values are equivalent) is a tricky subject. A cheap way to code -- though maybe not so cheap at run time -- is to compare JSON encodings...
const wholeObjectTest = (a, b) => JSON.stringify(a) === JSON.stringify(b);

Extracting unique values from an array of objects where value is a nested object in JavaScript

Let's say I've got the following array of objects in JavaScript:
const requests = [
{
id: 1,
person: {
id: 1
}
},
{
id: 2,
person: {
id: 1
}
},
{
id: 3,
person: {
id: 2
}
},
{
id: 4,
person: {
id: 3
}
},
{
id: 5,
person: {
id: 2
}
}
]
And what I've written below will go over each item in the array, and then create a new array containing just the person object.
const requestsPeopleIds = []
for (const request of requests) {
requestsPeopleIds.push(request.person.id)
}
I then take that new array and create another new array using Set to remove the duplicate ids:
const uniquePeopleIds = Array.from(new Set(requestsPeopleIds))
The final result is as I'd expect:
console.log(uniquePeopleIds) // [1, 2, 3]
where these are the unique ids of the people who made a request. So out of the 5 requests, these were made by 3 people.
There must be a more efficient way of doing this, so I'm reaching out to you stack overflow JS gurus.
Thanks in advance.
I think you got the basics. Here's a way to tighten the code:
var ids = new Set;
requests.forEach(i => ids.add(i.person.id));
You could also do this with map method and spread syntax ....
const requests = [{"id":1,"person":{"id":1}},{"id":2,"person":{"id":1}},{"id":3,"person":{"id":2}},{"id":4,"person":{"id":3}},{"id":5,"person":{"id":2}}]
const result = [...new Set(requests.map(({ person: { id }}) => id))]
console.log(result)
You can do it by making an object by the person's id as a key and get the keys of the object.
const requests = [{"id":1,"person":{"id":1}},{"id":2,"person":{"id":1}},{"id":3,"person":{"id":2}},{"id":4,"person":{"id":3}},{"id":5,"person":{"id":2}}]
// Take an empty object
const uniques = {};
// Iterate through the requests array and make person's id as a
// key of the object and put any value at this index (here I put 1).
requests.forEach(request => (uniques[request.person.id] = 1));
// Finally get the keys of the unique object.
console.log(Object.keys(uniques));
I've done some research and have inferred some interesting facts:
It looks like when we have very various data and larger array, then Set collection shows not best results. Set is very optimized collection, however, in my view, it should always check whether element is already added into Set. And this checking will take O(n) complexity. But we can use simple JavaScript object. Checking whether object contains key is O(1). So object will have huge advantage over Set.
foreach arrow function is very convenient, however, simple for loop is faster.
Adding console.log makes Set the most fastest solution, however, without console.log, the most fastest solution is combination of for loop and object.
So the most performant code without console.log() looks like this:
const hashMap = {};
const uniques = [];
for (let index = 0; index < requests.length; index++) {
if (!hashMap.hasOwnProperty(requests[index].person.id)){
hashMap[requests[index].person.id] = 1;
uniques.push(requests[index].person.id);
}
}
However, the most performant code with console.log() looks like this(I cannot understand the reason why it happens. It would be really great to know why it happens):
var ids = new Set;
requests.forEach(i => ids.add(i.person.id));
console.log(ids)
Tests:
with console.log
without console.log

problem Iterating through deep level object in javascript

I was trying one simple piece of code in which an array of objects are present and each object is having another array of products(with duplicate values).
I wanted to combine all the products array together without any duplicates.
Already reached half of iteration process but not able to remove duplicates, is there any way to iterate the values (as it is itself having key value as object 1 and its data..)
please suggest any other optimized way if possible. I'm new to JavaScript so pardon any silly mistakes made
Thanks in advance.
You can do it using concat, Set and Array.from:
const object1 = { products: ['1', '2', '3'] }
const object2 = { products: ['1', '2', '3', '4'] }
const object3 = { products: ['5'] }
// Merge all the products in one Array
const products = object1.products
.concat(object2.products)
.concat(object3.products);
// Create a Set, with the unique products
const set = new Set(products);
// Convert the Set to an Array
const uniqueProducts = Array.from(set);
console.log(uniqueProducts)
To remove duplicates you can use Set, it keeps all items unique, then you can cast it to Array.
Array.from(new Set(array))
there are many ways to achieve this:
you can use filter to remove duplicate elements:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
by reading in value, its index and array in filter function
a quick search link: https://codeburst.io/javascript-array-distinct-5edc93501dc4
you can always write a method for merge which would be good in terms of error handling, corner case checks, so that above action dont result into error,
example:
function MergeUniqueProductFromDictToArry(fromDict, productKey, toArray) {
//check if for the array in which we need to merge
if(!toArray) {
toArray = [];
}
//check for validity
if(!fromDict || !productKey || !fromDict[productKey] || fromDict[productKey].length == 0) {
return toArray;
}
for(var ix in fromDict[productKey]) {
//check if product already exist
if(toArray.indexOf(fromDict[productKey][ix]) === -1) {
toArray.push(fromDict[productKey][ix]);
}
}
return toArray;
}
var object1 = {products: ["p1", "p2", "p1"]};
var object2 = {products: ["p3", "p2"]};
var object3 = {products: ["p4", "p2"]};
var uniqueProducts = MergeUniqueProductFromDictToArry(object1, "products", null);
uniqueProducts = MergeUniqueProductFromDictToArry(object2, "products", uniqueProducts);
uniqueProducts = MergeUniqueProductFromDictToArry(object3, "products", uniqueProducts);
console.log(uniqueProducts);

How to `reduce` over an object?

I wonder how to use some reduce logic over an object rather than an array. Something like iterating over tuples represented by
[ object_property, property_value ]).
I tried some code like
var obj = { foo: 'bar', bar: 'baz' };
Array.prototype.reduce.call(obj, function(prev, val) {
console.log('new iteration');
// whatever code ...
return prev;
}, []);
but it doesn't perform any iteration. I can't understand why.
Maybe because the object properties are not enumerable?
Is there any way to run a reduce function on an object?
Note: There is no specific final purpose; I am exploring what's possible or what might be better patterns.
Array methods like reduce() can only operate on arrays or array-like objects (with length and numeric properties).
You can call Object.values() to get an array of an object's property values.
Several mistakes.
obj is not an array. Use .values()
The result of prev.push is not the array, is the result of the push method which is 1 (integer);
there is no var to get the final result.
Here is a working example:
var obj = { foo: 'bar', bar: 'baz' };
var res = Array.prototype.reduce.call(Object.values(obj), function(prev, val) {
prev.push(val);
return prev;
}, []);
console.log(res);
I don't know why you are using Array.prototype.reduce.call. There's no need to call it this way. Maybe you're experimenting with js or something.
Also prev is not a good name. Remember that it accumulates values, it's not just the previous interaction's value. Anyway, this is an easier way to do it:
var obj = { foo: 'bar', bar: 'baz' };
var res = Object.values(obj).reduce((acc, val)=>{
acc.push(val);
return acc;
}, []);
console.log(res);
But if this is the answer you want, reduce is not needed at all. Look:
var obj = { foo: 'bar', bar: 'baz' };
console.log(Object.values(obj));
You can run reduce() method on the array returned by Object.keys() function. It looks like this:
var obj = { foo: 'fooValue', bar: 'barValue' };
var str = Object.keys(obj).reduce((accum, key)=>{
return accum + obj[key];
}, '');
// str = 'fooValuebarValue';
You can use
Object.keys(obj) which will give you array of property names on that array you can apply any array method(predefined)
like this
var obj = { foo: 'bar', bar: 'baz' };
var prev=[] //let say prev is array in which you want to store some property value
Object.keys(obj).reduce(function(element) {
// you can have condition here as well
prev.push(obj[element]);
});
but be careful if you just want to populate another array then you can use other array method like filter,map,forEach(just for learning purpose because it similar to for loop)
it would make sense.

Grabbing names of object of out a JSON object in javascript and storing it in an String Array

So the issues that I am currently having is a string manipulation logic issue. My goal is to store the names of JSON objects in a string array. So it will be easier to access the data later on. But the current issue that I am running into is that the output is nothing that I want or understand of how it is getting it. Currently I am looking for the quotes between the object names and returning it to a string using str.substring, and storing it in an index of newArr. The output equals in 4th code snippet. I have also tried putting an underscore before and after the object name in the JSON object, then searching for the underscore. From my testing this will only work with the first name, which will return "foo" in index 0, while the rest of the indexes equal to '"_'. I know there is something wrong with my logic in the function, but I can not pinpoint what it is. Any help would be appreciated
This is the function that is being ran.
exports.jsonObjectToArray = function (objectToTurn){
var oldArr = JSON.stringify(objectToTurn).split(","),
firstIndex,
secondIndex,
newArr = [];
for(let i = 0; i < oldArr.length; i ++){
firstIndex = oldArr[i].indexOf("\"");
secondIndex = oldArr[i].indexOf(firstIndex, "\"");
newArr[i] = oldArr[i].substring(firstIndex, secondIndex);
}
return newArr;
}
When the function is ran oldArr will equal to this value.
[ '{"foo":"',
'"bar":"0"',
'"Mar":"0"',
'"Car":"0"}'
]
And my goal is to return this. Which will be stored in newArr.
[
"foo",
"bar",
"Mar",
"Car"
]
But after the function runs this is what I get returned.
[
'{"',
'bar":"0',
'Mar":"0',
'Car":"0'
]
To get the keys from an object, simply use Object.keys().
Quick example:
var obj = {
foo: '1',
bar: '2',
car: '3'
};
console.log(Object.keys(obj)); // ==> (3) ["foo", "bar", "car"]
let arr = [ '{"foo":"',
'"bar":"0"',
'"Mar":"0"',
'"Car":"0"}'
]
let arr1 = arr.map(el => el.split('"')[1])

Categories

Resources