Hi I have following objects in an array and they appear like this:
[{col1:abc}
{col2:def}
{col1:ghi}]
What I want to do is if the same key is coming again I should overwrite it so it becomes
[{col1:ghi}
{col2:def}]
instead of appending another key value pair.
I am thinking of something like to overwrite
[col1:{col1:ghi}
col2:{col2:def}]
so that i can easily iterate over them in future.
Is there any way to put my keys in this way by using map or something similar?
Thank you
I'm going to assume all your objects look like the ones you provided in your question, i.e. they each are a single key-value pair (like {col1: "abc"})
We first need to loop over all the objects in your array, combining them into one large object. Since an object cannot have two identical keys, this has the effect of overwriting values associated with a key that occur earlier in the array with ones that associate with the same key, but occur later. This can be achieved with:
const unifiedObj = arr.reduce((acc, obj) => Object.assign(acc, obj), {})
reduce is a way of "looping" over the items in an array (well, not exactly, but you can think of it this way for now). Object.assign is a way to merge two objects. You should look these up in the MDN docs.
So now, if your original array looked like this:
[
{col1:"abc"}
{col2:"def"}
{col1:"ghi"}
]
The "unified" object will look like this:
{
col1: "ghi",
col2: "def"
}
Next, since you want an array of 'single key-value pair objects' as your final result, instead of this unified object, we're going to have to extract each key-value pair in the unified object into a new object, and collect all those new objects into an array. That's what this statement does.
const result = Object.keys(unifiedObj).map(k => ({k: unifiedObj[k]}))
Object.keys gives you all the keys of an object as an array. map transforms an array into another array, using the function supplied as its argument. Look these up too.
At the end, result will be an array that looks like this:
[
{ col1: "ghi" },
{ col2: "def" }
]
which seems to be what you wanted. Do note that the objects in the array might be in a different order from what you expect, i.e. the final array may also look like this:
[
{ col2: "def" },
{ col1: "ghi" }
]
This is quite an easy task! The explanation is in the code comments.
const arr = [{col1:'abc'},
{col2:'def'},
{col1:'ghi'}]
arr.forEach((item, index) => {
// get the key `col1`, `col2` etc. (only works if there is one key in the object!)
const key = Object.keys(item)[0]
// now check if the key was previously encountered
for (i = 0; i < index; i++) {
if (arr[i][key] !== undefined) {
// the same key was found in the already processed chunk of the array! Rewrite it with the latter value!
arr[i][key] = item
}
}
})
console.log(arr)
I think you probably want to produce a single object from your array of objects and retain all values for the same key, so that
[
{col1: 'abc'},
{col2: 'def'},
{col1: 'ghi'}
]
becomes
{
col1: ['abc', 'ghi'],
col2: ['def']
}
if that sounds right, then here's how to do it:
const arr = [
{col1: 'abc'},
{col2: 'def'},
{col1: 'ghi'}
];
const result = arr.reduce((memo, o) => {
Object.entries(o).forEach(([key, val]) => {
memo[key] = memo[key] || [];
memo[key].push(val);
});
return memo;
}, {});
console.log(result)
Related
I'm currently learning about the reduce method in JS, and while I have a basic understanding of it, more complex code completely throws me off. I can't seem to wrap my head around how the code is doing what it's doing. Mind you, it's not that the code is wrong, it's that I can't understand it. Here's an example:
const people = [
{ name: "Alice", age: 21 },
{ name: "Max", age: 20 },
{ name: "Jane", age: 20 },
];
function groupBy(objectArray, property) {
return objectArray.reduce((acc, obj) => {
const key = obj[property];
const curGroup = acc[key] ?? [];
return { ...acc, [key]: [...curGroup, obj] };
}, {});
}
const groupedPeople = groupBy(people, "age");
console.log(groupedPeople);
// {
// 20: [
// { name: 'Max', age: 20 },
// { name: 'Jane', age: 20 }
// ],
// 21: [{ name: 'Alice', age: 21 }]
// }
Now the reduce method as I understand it, takes an array, runs some provided function on all the elements of the array in a sequential manner, and adds the result of every iteration to the accumulator. Easy enough. But the code above seems to do something to the accumulator as well and I can't seem to understand it. What does
acc[key] ?? []
do?
Code like this make it seem like a breeze:
const array1 = [1, 2, 3, 4];
// 0 + 1 + 2 + 3 + 4
const initialValue = 0;
const sumWithInitial = array1.reduce(
(accumulator, currentValue) => accumulator + currentValue,
initialValue
);
console.log(sumWithInitial);
// Expected output: 10
But then I see code like in the first block, I'm completely thrown off. Am I just too dumb or is there something I'm missing???
Can someone please take me through each iteration of the code above while explaining how it
does what it does on each turn? Thanks a lot in advance.
You are touching on a big problem with reduce. While it is such a nice function, it often favors code that is hard to read, which is why I often end up using other constructs.
Your function groups a number of objects by a property:
const data = [
{category: 'catA', id: 1},
{category: 'catA', id: 2},
{category: 'catB', id: 3}
]
console.log(groupBy(data, 'category'))
will give you
{
catA: [{category: 'catA', id: 1}, {category: 'catA', id: 2}],
catB: [{category: 'catB', id: 3}]
}
It does that by taking apart the acc object and rebuilding it with the new data in every step:
objectArray.reduce((acc, obj) => {
const key = obj[property]; // get the data value (i.e. 'catA')
const curGroup = acc[key] ?? []; // get collector from acc or new array
// rebuild acc by copying all values, but replace the property stored
// in key with an updated array
return { ...acc, [key]: [...curGroup, obj] };
}, {});
You might want to look at spread operator (...) and coalesce operator (??)
Here is a more readable version:
objectArray.reduce((groups, entry) => {
const groupId = entry[property];
if(!groups[groupId]){
groups[groupId] = [];
}
groups[groupId].push(entry);
return groups;
}, {});
This is a good example where I would favor a good old for:
function groupBy(data, keyProperty){
const groups = {}
for(const entry of data){
const groupId = entry[keyProperty];
if(!groups[groupId]){
groups[groupId] = [];
}
groups[groupId].push(entry);
}
return groups;
}
Pretty much the same number of lines, same level of indentation, easier to read, even slightly faster (or a whole lot, depending on data size, which impacts spread, but not push).
That code is building an object in the accumulator, starting with {} (an empty object). Every property in the object will be a group of elements from the array: The property name is the key of the group, and the property value is an array of the elements in the group.
The code const curGroup = acc[key] ?? []; gets the current array for the group acc[key] or, if there isn't one, gets a new blank array. ?? is the "nullish coalescing operator." It evaluates to its first operand if that value isn't null or undefined, or its second operand if the first was null or undefined.
So far, we know that obj[property] determines the key for the object being visited, curGroup is the current array of values for that key (created as necessary).
Then return { ...acc, [key]: [...curGroup, obj] }; uses spread notation to create a new accumulator object that has all of the properties of the current acc (...acc), and then adds or replaces the property with the name in key with a new array containing any previous values that the accumulator had for that key (curGroup) plus the object being visited (obj), since that object is in the group, since we got key from obj[property].
Here's that again, related to the code via comments. I've split out the part creating a new array [...curGroup, obj] from the part creating a new accumulator object for clarity:
function groupBy(objectArray, property) {
return objectArray.reduce(
(acc, obj) => {
// Get the value for the grouping property from this object
const key = obj[property];
// Get the known values array for that group, if any, or
// a blank array if there's no property with the name in
// `key`.
const curGroup = acc[key] ?? [];
// Create a new array of known values, adding this object
const newGroup = [...curGroup, obj];
// Create and return a new object with the new array, either
// adding a new group for `key` or replacing the one that
// already exists
return { ...acc, [key]: newGroup };
},
/* The starting point, a blank object: */ {}
);
}
It's worth noting that this code is very much written with functional programming in mind. It uses reduce instead of a loop (when not using reduce, FP usually uses recursion rather than loops) and creates new objects and arrays rather than modifying existing ones.
Outside of functional programming, that code would probably be written very differently, but reduce is designed for functional programming, and this is an example of that.
Just FWIW, here's a version not using FP or immutability (more on immutability below):
function groupBy(objectArray, property) {
// Create the object we'll return
const result = {};
// Loop through the objects in the array
for (const obj of objectArray) {
// Get the value for `property` from `obj` as our group key
const key = obj[property];
// Get our existing group array, if we have one
let group = result[key];
if (group) {
// We had one, add this object to it
group.push(obj);
} else {
// We didn't have one, create an array with this object
// in it and store it on our result object
result[key] = [obj];
}
}
return result;
}
In a comment you said:
I understand the spread operator but it's use in this manner with the acc and the [key] is something I'm still confused about.
Yeah, there are a lot of things packed into return { ...acc, [key]: [...curGroup, obj] };. :-) It has both kinds of spread syntax (... isn't an operator, though it's not particularly important) plus computed property name notation ([key]: ____). Let's separate it into two statements to make it easier to discuss:
const updatedGroup = [...curGroup, obj];
return { ...acc, [key]: updatedGroup };
TL;DR - It creates and returns a new accumulator object with the contents of the previous accumulator object plus a new or updated property for the current/updated group.
Here's how that breaks down:
[...curGroup, obj] uses iterable spread. Iterable spread spreads out the contents of an iterable (such as an array) into an array literal or a function call's argument list. In this case, it's spread into an array literal: [...curGroup, obj] says "create a new array ([]) spreading out the contents of the curGroup iterable at the beginning of it (...curGroup) and adding a new element at the end (, obj).
{ ...acc, ____ } uses object property spread. Object property spread spreads out the properties of an object into a new object literal. The expression { ...acc, _____ } says "create a new object ({}) spreading out the properties of acc into it (...acc) and adding or updating a property afterward (the part I've left as just _____ for now)
[key]: updatedGroup (in the object literal) uses computed property name syntax to use the value of a variable as the property name in an object literal's property list. So instead of { example: value }, which creates a property with the actual name example, computed property name syntax puts [] around a variable or other expression and uses the result as the property name. For instance, const obj1 = { example: value }; and const key = "example"; const obj2 = { [key]: value }; both create an object with a propety called example with the value from value. The reduce code is using [key]: updatedGroup] to add or update a property in the new accumulator whose name comes from key and whose value is the new group array.
Why create a new accumulator object (and new group arrays) rather than just updating the one that the code started with? Because the code is written such that it avoids modifying any object (array or accumulator) after creating it. Instead of modifying one, it always creates a new one. Why? It's "immutable programming," writing code that only ever creates new things rather than modifying existing things. There are good reasons for immutable programming in some contexts. It reduces the possibilities of a change in code in one place from having unexpected ramifications elsewhere in the codebase. Sometimes it's necessary, because the original object is immutable (such as one from Mongoose) or must be treated as though it were immutable (such as state objects in React or Vue). In this particular code it's pointless, it's just style. None of these objects is shared anywhere until the process is complete and none of them is actually immutable. The code could just as easily use push to add objects to the group arrays and use acc[key] = updatedGroup; to add/update groups to the accumulator object. But again, while it's pointless in this code, there are good uses for immutable programming. Functional programming usually adheres to immutability (as I understand it; I haven't studied FP deeply).
In its most basic form, having an array of objects:
let arr = [
{val:"a"},
{val:"b"}
];
How can destructuring be used, to obtain only the values ['a', 'b'].
getting the first value is easy:
let [{val:res}] = arr; //res contains 'a'
Obtaining all values inside the array can be done with the rest operator:
let [...res] = arr; //res contains all objects
Combining those, I expected to be able to use:
let [...{val:res}] = arr; //undefined, expected all 'val's (['a', 'b'])
The above returns undefined (Tested in FF). Some further testing seems to indicate that adding the rest operator when using an object destructuring as well doesn't use the iteration, but gets back the original object, e.g. let [...{length:res}] = arr; //res= 2. Some other trials, such as let [{val:...res}] = arr; or let [{val}:...res] = arr; produce syntax errors.
It's easy enough to do with other methods, such as using map on the array, but mostly I stumble upon this problem while destructuring multiple levels (an array with objects which have their own property containing an array). Therefore I'm really trying to get around how to do it solely with destructuring.
For convenience: a test fiddle
edit
My apologies if I failed to explain the goal of the question. I'm not looking for a solution to a specific problem, only to find the correct syntax to use when destructuring.
Otherwise formulated, a first question would be: in the example above, why doesn't let [...{val:res}] = arr; return all values (['a', 'b']). The second question would be: what is the proper syntax to use a rest operator with a nested object destructuring? (pretty sure I've gotten some definitions mixed up here). It seems that the latter is not supported, but I haven't come across any documentation that (and why) it wouldn't be.
Why doesn't let [...{val:res}] = arr; return all values (['a', 'b'])?
You seem to confuse the rest syntax with array comprehensions.
If you assign a value to [someElements, ...someExpression], the value is tested to be iterable and then each element generated by the iterator is assigned to the respective someElements variable. If you use the rest syntax in the destructuring expression, an array is created and the iterator is ran till its end while filling the array with the generated values. Then that array is assigned to the someExpression.
All of these assignment targets can be other destructuring expressions (arbitrarily nested and recursively evaluated), or references to variable or properties.
So if you do let [...{val:res}] = arr, it will create an array and fill that with all the values from the iterator of arr:
let {val:res} = Array.from(arr[Symbol.iterator]())
You can see now why that ends up with undefined, and why using something like [...{length:res}] does yield a result. Another example:
let [{val:res1}, ...{length: res2}] = arr;
console.log(res1) // 'a'
console.log(res2) // 1 (length of `[{val: 'b'}]`)
How can destructuring be used to obtain only the values ['a', 'b']?
Not at all. Use the map method.
You can destructure nested objects like this
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Nested_object_and_array_destructuring
let arr = [
{val:"a"},
{val:"b"}
];
const [{val: valueOfA}, {val: valueOfB}] = arr
console.log(
valueOfA, valueOfB
)
Beside mapping with a callback for the value
let arr = [{ val: "a" }, { val: "b" }];
console.log(arr.map(o => o.val));
you could use deconstructiong inside of the paramter list and use only the value to return.
let arr = [{ val: "a" }, { val: "b" }];
console.log(arr.map(({val}) => val));
At this point of time you can use both For of loop with ES6 Object destructuring.
let arr = [{val:"a"},{val:"b"}];
for (const item in arr){
const {val} = arr[item];
console.log(val);
}
You can declare assignment target before destructuring assignment; at destructuring target, set values of assignments target indexes by from destructuring source
let arr1 = [{val: "a"}, {val: "b"}];
let arr2 = [{"foo":1,"arr":[{"val":"a"},{"val":"b"}]}
, {"foo":2,"arr":[{"val":"c"},{"val":"d"}]}];
let [res1, res2] = [[], []];
[{val: res1[0]}, {val: res1[1]}] = arr1;
[{arr: [{val:res2[0]}, {val:res2[1]}]}
, {arr: [{val:res2[2]}, {val:res2[3]}]}] = arr2;
console.log(res1, res2);
You can alternatively use rest element at target to collect values at source by including comma operator following object pattern to return value pulled from object
let arr = [{val: "a"}, {val: "b"}];
let [...res] = [({val} = arr[0], val), ({val} = arr[1], val)];
console.log(res)
This might be a noob question for you guys but I just want to clarify something. I'm new to JS and I know that arrays are just objects with fields as indexes.
I have some lines of code here. The objective is pretty easy, is to pass a parameter function to an array and map it to another one.
My confusion is that the _obj is declared as an object with _obj = {}, and we have to do _obj[obj.key] = obj.value to map the keys and values. What is actually going around here?
It makes me feel like there are two nested arrays and it feels gross. I hope you understand me and I just need to know if there is another way or what actually is going on.
Stay at home guys!
Thanks in advance.
const objArray = [
{key:1, value:10},
{key:2, value:20},
{key:3, value:30},
{key:4, value:40}
];
const newArray = objArray.map(obj => {
let _obj = {};
_obj[obj.key] = obj.value;
return _obj;
});
console.log(newArray);
//Array [Object { 1: 10 }, Object { 2: 20 }, Object { 3: 30 }, Object { 4: 40 }]
I just need to know if there is another way or what actually is going
on.
When you do:
_obj[obj.key] = obj.value;
... you are setting a key/property on the object _obj to hold a particular value. In this case, the key you are setting is the value of obj.key and the value is obj.value. The values of obj.key and obj.value changes depending on what object you are iterated on in the .map() callback. For example, if you are looking at the first object in your array, obj.key would be 1 and obj.value would be 10. Thus, doing _obj[obj.key] = obj.value would be equivalent to _obj[1] = 10, which sets the key of 1 to have the value of 10 on the _obj object:
{
1: 10
}
This process occurs for each object in your array.
As for a different approach, you could use computed-property names introduced in ES6 with destructuring assignment to return an object literal with the key property as the key for the new object literal and the value property and the actual value for the new object:
const objArray = [
{key:1, value:10},
{key:2, value:20},
{key:3, value:30},
{key:4, value:40}
];
const newArray = objArray.map(({key, value}) => ({[key]: value}));
console.log(newArray);
In javascript, just like java, everything that is not a primitive is an object. Arrays are actually an object that just have some utility functions added in, such as the length attribute, and .map. To access a property, you can either do obj.prop, or use an arbitrary string like python's dictionaries like obj["prop"].
Array is special object, In which all key should be Number. The array has some pre-defined method, Which is made to iterate and perform similar operations. The array is linear and implements stack implementation. Array element could be any other object or Array.
To understand:
const object = {
1: "something",
2: "something2"
}
const object2 = {
"1_1": "something",
"2_1": "something2"
}
// console.log(object[1])
// console.log(object[2])
// console.log(object2["2_1"]) // Key string
// console.log(object2["1_1"])
// object.forEach(console.log) // Error
const array = ["something", "something2"]
const array2 = []
array2["1_1"] = "something"
array2["2_1"] = "something2"
console.log(array[0]) // index start with 0, default
console.log(array[1])
console.log(array2["2_1"]) // Key string
console.log(array2["1_1"]) // Works
array.forEach(console.log) // No error, something 0 [ 'something', 'something2' ] something2 1 [ 'something', 'something2' ]
array2.forEach(console.log) // No error, // No output
Here is the variant with reduce method:
const objArray = [
{key:1, value:10},
{key:2, value:20},
{key:3, value:30},
{key:4, value:40}
];
const newArray = objArray.reduce((acc, rec) => { return {...acc, [rec.key]: rec.value} }, []);
console.log(JSON.stringify(newArray))
// {"1":10,"2":20,"3":30,"4":40}
I have two arrays and I want to form an array of objects such that the new array of obj has two keys with first key being filled with elements of first array and second key having arrays elements of second array. Can this be done using the map function. I found the closest answer as this:-
Merge two arrays into an array of objects with property values
eg.:-
ar1=[];
ar2=[];
armixed=[{ar1element,ar2element},{}......]
But it uses angular JS I just want to use pure JS.
I'm not sure what your output should be but the one you provided seems invalid. I have modified the output format to be valid.
For the task you have the solution is to zip the arrays, however, JS has no inbuilt zip function, so we can emulate it via map function:
var ar1 = ['a1', 'a2', 'a3', 'a4', 'a5'];
var ar2 = ['b1', 'b2', 'b3', 'b4', 'b5'];
var armixed = ar1.map(function (x, i) {
return [x, ar2[i]]
});
Output will be:
armixed = [
["a1", "b1"]
["a2", "b2"]
["a3", "b3"]
["a4", "b4"]
["a5", "b5"]
]
If you want objects in your output (rather than arrays), you can just edit the return statement above to something like:
return { categories: x, catid: ar2[i] }
Just to clarify, do you want an object for each unique possible combination of the arrays? If so, nesting two map functions should do the trick:
let newArray = [{}]
arr1.map(i => {
arr2.map (j => {
newArray.push({prop1: i, prop2: j})
})
})
In its most basic form, having an array of objects:
let arr = [
{val:"a"},
{val:"b"}
];
How can destructuring be used, to obtain only the values ['a', 'b'].
getting the first value is easy:
let [{val:res}] = arr; //res contains 'a'
Obtaining all values inside the array can be done with the rest operator:
let [...res] = arr; //res contains all objects
Combining those, I expected to be able to use:
let [...{val:res}] = arr; //undefined, expected all 'val's (['a', 'b'])
The above returns undefined (Tested in FF). Some further testing seems to indicate that adding the rest operator when using an object destructuring as well doesn't use the iteration, but gets back the original object, e.g. let [...{length:res}] = arr; //res= 2. Some other trials, such as let [{val:...res}] = arr; or let [{val}:...res] = arr; produce syntax errors.
It's easy enough to do with other methods, such as using map on the array, but mostly I stumble upon this problem while destructuring multiple levels (an array with objects which have their own property containing an array). Therefore I'm really trying to get around how to do it solely with destructuring.
For convenience: a test fiddle
edit
My apologies if I failed to explain the goal of the question. I'm not looking for a solution to a specific problem, only to find the correct syntax to use when destructuring.
Otherwise formulated, a first question would be: in the example above, why doesn't let [...{val:res}] = arr; return all values (['a', 'b']). The second question would be: what is the proper syntax to use a rest operator with a nested object destructuring? (pretty sure I've gotten some definitions mixed up here). It seems that the latter is not supported, but I haven't come across any documentation that (and why) it wouldn't be.
Why doesn't let [...{val:res}] = arr; return all values (['a', 'b'])?
You seem to confuse the rest syntax with array comprehensions.
If you assign a value to [someElements, ...someExpression], the value is tested to be iterable and then each element generated by the iterator is assigned to the respective someElements variable. If you use the rest syntax in the destructuring expression, an array is created and the iterator is ran till its end while filling the array with the generated values. Then that array is assigned to the someExpression.
All of these assignment targets can be other destructuring expressions (arbitrarily nested and recursively evaluated), or references to variable or properties.
So if you do let [...{val:res}] = arr, it will create an array and fill that with all the values from the iterator of arr:
let {val:res} = Array.from(arr[Symbol.iterator]())
You can see now why that ends up with undefined, and why using something like [...{length:res}] does yield a result. Another example:
let [{val:res1}, ...{length: res2}] = arr;
console.log(res1) // 'a'
console.log(res2) // 1 (length of `[{val: 'b'}]`)
How can destructuring be used to obtain only the values ['a', 'b']?
Not at all. Use the map method.
You can destructure nested objects like this
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Nested_object_and_array_destructuring
let arr = [
{val:"a"},
{val:"b"}
];
const [{val: valueOfA}, {val: valueOfB}] = arr
console.log(
valueOfA, valueOfB
)
Beside mapping with a callback for the value
let arr = [{ val: "a" }, { val: "b" }];
console.log(arr.map(o => o.val));
you could use deconstructiong inside of the paramter list and use only the value to return.
let arr = [{ val: "a" }, { val: "b" }];
console.log(arr.map(({val}) => val));
At this point of time you can use both For of loop with ES6 Object destructuring.
let arr = [{val:"a"},{val:"b"}];
for (const item in arr){
const {val} = arr[item];
console.log(val);
}
You can declare assignment target before destructuring assignment; at destructuring target, set values of assignments target indexes by from destructuring source
let arr1 = [{val: "a"}, {val: "b"}];
let arr2 = [{"foo":1,"arr":[{"val":"a"},{"val":"b"}]}
, {"foo":2,"arr":[{"val":"c"},{"val":"d"}]}];
let [res1, res2] = [[], []];
[{val: res1[0]}, {val: res1[1]}] = arr1;
[{arr: [{val:res2[0]}, {val:res2[1]}]}
, {arr: [{val:res2[2]}, {val:res2[3]}]}] = arr2;
console.log(res1, res2);
You can alternatively use rest element at target to collect values at source by including comma operator following object pattern to return value pulled from object
let arr = [{val: "a"}, {val: "b"}];
let [...res] = [({val} = arr[0], val), ({val} = arr[1], val)];
console.log(res)