Convert array of objects to grouped object with values in lodash - javascript

I’m looking for a compact lodash solution to take an array of objects and then create a new object with the keys of the objects in the array and the unique values for each key.
[
{
color: "black",
type: "bag",
},
{
color: "red",
type: "pants",
},
{
color: "black",
type: "jacket",
},
]
Desired output:
{
color: ["black", "red"],
type: ["bag", "pants", "jacket"],
}
Thanks!

You can solve this using lodash#flatMap with a lodash#toPairs iteratee, to create a flattened version of an array pair of keys and values. Use lodash#groupBy with an iteratee function that removes the key of the array pairs, Array#shift, and use such value as the grouping key for the collection. Lastly, use lodash#mapValues with an iteratee that is composed of lodash#flatten and lodash#uniq, to flatten the array and only get the unique values for each grouped array. Note that the function composition uses lodash#flow to compose successive function arguments to form one functional result.
var result = _(array)
.flatMap(_.toPairs)
.groupBy(v => v.shift())
.mapValues(_.flow(_.flatten, _.uniq))
.value();
var array = [
{
color: "black",
type: "bag",
},
{
color: "red",
type: "pants",
},
{
color: "black",
type: "jacket",
}
];
var result = _(array)
.flatMap(_.toPairs)
.groupBy(v => v.shift())
.mapValues(_.flow(_.flatten, _.uniq))
.value();
console.log(result);
.as-console-wrapper{min-height:100%;top:0}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>

I can't see a function that'll do this for you, so here's a solution that solves the problem in a number of straightforward steps using only lodash functions.
const listOfPairs = _.flatten(_.map(input, _.toPairs))
transforms the list of objects into a list of pairs of [key, value]
listOfPairs = [
[ 'color', 'black' ],
[ 'type', 'bag' ],
[ 'color', 'red' ],
[ 'type', 'pants' ] ]
now, we can group these up by the values in the first position in each pair.
const indexByKeyToValues = _.toPairs(_.groupBy(listOfPairs, _.head))
which gives us
indexByKeyToValues = [
[ 'color', [ ['color', 'black'], ['color', 'red'] ] ],
[ 'type', [ ['type', 'bag'], ['type', 'pants'] ] ] ]
then, map over the value arrays to pick the last element (the original values in the input maps)
const pairsOfKeyAndValue = _.map(indexByKeyToValues, ([k, vs]) => [k, _.map(vs, _.last)])
which is almost there
pairsOfKeyAndValue = [
[ 'color', [ 'black', 'red' ] ],
[ 'type', [ 'bag', 'pants' ] ] ]
we just need to rebuild an object using these pairs
const result = _.fromPairs(pairsOfKeyAndValue)
The whole "transforming a map to and from sequences of pairs" trick is really common in functional programming to do this kind of processing. Once you've done that, figuring out the rest of the steps isn't too tricky.
Hopefully this gives you a general idea you can use to solve these kinds of problems in future.

Use what's built-in. It's faster, shorter, and easier to reason from.
const arr = [{color: "black",type: "bag",},{color: "red",type: "pants",},{color: "black",type:"jacket",}],
obj = arr.reduce((h, y) => {
Object.keys(y).forEach(k => {
if (!h[k]) {
h[k] = []
h[k].push(y[k])
} else if (!h[k].includes(y[k])) h[k].push(y[k])
})
return h
}, {})
console.log(obj)

Related

How to merge an array of objects into one array and then filter out the duplicates

Firstly, I am trying to merge an array of many objects into a single array with every key in each object.
Lastly, any duplicate items in the array should be removed as well as any elements named "name".
Input:
const data = [
{
name: '10/20',
Tyler: 1,
Sonia: 0,
Pedro: 0,
},
{
name: '10/23',
Tyler: 0.5,
Sonia: 0.25,
Pedro: 0.75,
George: 0.5,
},
];
Output:
["Tyler", "Sonia", "Pedro", "George"]
This is what I've tried so far:
const mergedData = data.reduce((prev, cur) => {
const obj = cur[0];
const keys = Object.keys(obj);
const names = keys.splice(1);
return { names };
}, []);
I am trying to capture any key name other than "name" and add it to the final array. However, this is where I get stuck because I get this error, TypeError: Cannot convert undefined or null to object
Note: Objects may be different lengths, contain a mix of names, but never any duplicates.
An option is to find all keys put in a set and remove the name key
const data = [
{
name: '10/20',
Tyler: 1,
Sonia: 0,
Pedro: 0,
},
{
name: '10/23',
Tyler: 0.5,
Sonia: 0.25,
Pedro: 0.75,
George: 0.5,
},
];
const set = new Set(data.reduce((acc, i) => [...acc, ...Object.keys(i)], []));
set.delete('name');
const result = [...set];
console.log(result);
If you have access to ES6 methods, you can do this using a Set (unique values are ensured at creation) and converting it back into an array if you want through Destructuring.
data = [{name: '0', Tyler: '1', Dan: '2', Carl: '3'}, {name: '0', Tyler: '1', Dan: '2', Eric: '3', Danny: '4'}];
const output = (data) => {
let output = [];
// This makes sure you get each array and then strips just the keys as desired
data.forEach(item => {
output = output.
concat(Object.keys(item))
});
// This creates the set, strips our dups, and then spreads itself into an array
return [...new Set(output)]
// Strip out the 'name' key as needed
// NOTE: This should be a param instead of hard-coded, but this is easier to show
.filter(res => res != 'name');
}
console.log(output(data));
This should be fairly performant considering it only navigates the full array one time and each object itself shouldn't have millions of properties to cause .keys() any issues.

How to retain objects with a specific unique attribute in an array in javascript?

I have an array like so:
var myarray = [
{"scaleId":"001","currentWeight":0.200}
{"scaleId":"002","currentWeight":0.300},
{"scaleId":"001","currentWeight":0.255},
{"scaleId":"002","currentWeight":0.000},
{"scaleId":"003","currentWeight":0.340},
]
and I want to retain the objects which have unique scaleId. So, for the above example, the output would like ( any random object can be retained if the scaleId is duplicate):
myarray = [
{"scaleId":"001","currentWeight":0.200}
{"scaleId":"002","currentWeight":0.300},
{"scaleId":"003","currentWeight":0.340},
]
I'm already using underscore library in my application, so I'm fine with the implementation using underscore. I had come up with a solution but it is not working as expected. So, any help will be great.
Maps and Sets are often the appropriate structures to maintain uniqueness. Here is a simple function that uses a Map to maintain a single unique value given a property name:
const uniqByProp = (prop) => (xs) =>
[... new Map (xs .map ((x) => [x [prop], x])) .values ()]
var myarray = [{scaleId: "001", currentWeight: 0.200}, {scaleId: "002", currentWeight: 0.300}, {scaleId: "001", currentWeight: 0.255}, {scaleId: "002", currentWeight: 0.000}, {scaleId: "003", currentWeight: 0.340}]
console .log (
uniqByProp ('scaleId') (myarray)
)
.as-console-wrapper {min-height: 100% !important; top: 0}
This version keeps the last matching value. If you want the first matching one, it would be only slightly more complex.
Use reduce and inside callback check if in accumulator object there is a key with same scaleId. If so then add the value to that key. Finally use Object.values to create an array of values
var myarray = [{
"scaleId": "001",
"currentWeight": 0.200
},
{
"scaleId": "002",
"currentWeight": 0.300
},
{
"scaleId": "001",
"currentWeight": 0.255
},
{
"scaleId": "002",
"currentWeight": 0.000
},
{
"scaleId": "003",
"currentWeight": 0.340
}
]
let data = myarray.reduce((acc, curr) => {
if (!acc[curr.scaleId]) {
acc[curr.scaleId] = curr
}
return acc;
}, {});
console.log(Object.values(data))
You can do it this way (if you are using ES6/ES2015 or later)
Using Set to filter unique scaleIds first and feeding a new array with the Array.find method
var myarray = [
{"scaleId":"001","currentWeight":0.200},
{"scaleId":"002","currentWeight":0.300},
{"scaleId":"001","currentWeight":0.255},
{"scaleId":"002","currentWeight":0.000},
{"scaleId":"003","currentWeight":0.340},
]
let scaleIds = [...new Set(myarray.map(item => item.scaleId))];
let filtered = []
scaleIds.forEach(scaleId => filtered.push(myarray.find(item => item.scaleId === scaleId)))
console.log(filtered)
You can also use Array.prototype.reduceRight() to simplify aggregation logic:
let myarray = [
{ scaleId: '001', currentWeight: 0.2 },
{ scaleId: '002', currentWeight: 0.3 },
{ scaleId: '001', currentWeight: 0.255 },
{ scaleId: '002', currentWeight: 0 },
{ scaleId: '003', currentWeight: 0.34 }
];
myarray = Object.values(
myarray.reduceRight(
(acc, cur) => (acc[cur.scaleId] = cur, acc),
{}
)
).reverse(); // optional if order of output matters to you
console.log(myarray);
or construct a Map from key-value pairs generated using Array.prototype.map():
let myarray = [
{ scaleId: '001', currentWeight: 0.2 },
{ scaleId: '002', currentWeight: 0.3 },
{ scaleId: '001', currentWeight: 0.255 },
{ scaleId: '002', currentWeight: 0 },
{ scaleId: '003', currentWeight: 0.34 }
];
myarray = Array.from(
new Map(
myarray.map(
val => [val.scaleId, val]
).reverse()
).values()
).reverse(); // optional if order of output matters to you
console.log(myarray);
We use Array.prototype.reverse() on the array passed to the constructor to ensure that the insertion order allows earlier values to take precedence over later values.

How to map an array to another array and reset its keys in Javascript?

I have an object that has unique keys and each key holds an object:
var object = { 'a': {
source: '5279edf0-cd7f-11e3-af07-59475a41e2e9',
target: 'f6b3faa1-ad86-11e3-9409-3dbc47429e9f',
id: [ 'bf504d02-81e2-4a92-9c5c-8101943dc36d' ],
edge_context: [ 'small' ],
statement_id: [ '09b05bc0-20ab-11e9-a5b3-9fb3da66a7cb' ],
weight: 2
},
'b': {
source: '5279edf1-cd7f-11e3-af07-59475a41e2e9',
target: 'f6b3faa1-ad86-11e3-9409-3dbc47429e9f',
id: [ 'de769846-9145-40f8-ab2d-91c0d9b82b27',
'd5723929-71a0-4dfe-bf03-94d43e358145' ],
edge_context: [ 'small' ],
statement_id:
[ '09b05bc0-20ab-11e9-a5b3-9fb3da66a7cb',
'62671510-20ab-11e9-8cbf-ef11fdb08712' ],
weight: 6
}
}
var newArray = [];
for (let item of object) {
newArray(item);
}
console.log(newArray);
I want to map it to another array where the keys will be in a sequence 0, 1, 2 etc as the usual array
I tried to use this function above but it's not working saying "object is not iterable" so how to iterate the object?
Maybe:
const mappedObject = Object.keys(object).map(
k => object[k]
)
As others have pointed out, change the structure. It could be in the following way (you will obtain an array of objects, which you will be able to access using indexes like 0, 1, 2, etc):
var objt = [
{"a": {
"source": "5279edf0-cd7f-11e3-af07-59475a41e2e9",
"target": "f6b3faa1-ad86-11e3-9409-3dbc47429e9f",
"id": [ "bf504d02-81e2-4a92-9c5c-8101943dc36d" ],
"edge_context": [ "small" ],
"statement_id": [ "09b05bc0-20ab-11e9-a5b3-9fb3da66a7cb" ],
"weight": 2
}
},
{"b": {
"source": "5279edf1-cd7f-11e3-af07-59475a41e2e9",
"target": "f6b3faa1-ad86-11e3-9409-3dbc47429e9f",
"id": [ "de769846-9145-40f8-ab2d-91c0d9b82b27",
"d5723929-71a0-4dfe-bf03-94d43e358145" ],
"edge_context": [ "small" ],
"statement_id":
[ "09b05bc0-20ab-11e9-a5b3-9fb3da66a7cb",
"62671510-20ab-11e9-8cbf-ef11fdb08712" ],
"weight": 6
}
}
];
var newArray = objt.map(element => {
const firstProperty = Object.keys(element)[0];
let objectInfo = element[firstProperty];
console.log(objectInfo);
return objectInfo;
});
console.log(newArray);
What happens here is, the only field of each object is not named the same (in one object is "a", the next one is "b", and so on) so we need to figure out to get the only property of each object in the initial array, which contains the information you need to put in another array. For doing this. Object.keys() returns you an array of the properties of an object. Considering the scenario in which we only have one property per object, we get it using Object.keys(element)[0].
Finally, we just use .map() to generate a new array.
I would use Object.values(object) but it's not supported by IE (there is a polyfill for that). Or would use Object.getOwnPropertyNames (which is supported by IE) to convert the keys to an array and map the array to another which containing the values.
var newArray = Object.getOwnPropertyNames(object).map(key => object[key])

JSON manipulation - javascript - adding new keys and shifting data

I'm learning to manipulate JSON data and I am stuck trying to figure out how to cajole the following JSON into what I want as shown below:
Any pointers to function/terms/concepts that I should learn for this sort of problem would be greatly appreciated! Thanks
JSON object
{
car: 1,
van: 5,
cat: 99999999999999999999999
}
Desired outcome:
items: [
{ "type": "car", "value": "1"},
{ "type": "van", "value": "5"},
{ "type": "cat", "value": "99999999999999999999999"}
]
You can use a combination of Object.entries and Array.prototype.map:
const obj = { car: 1, van: 5, cat: 99999999999999999999999 };
let list = Object.entries(obj) // [["car",1],["van",5],["cat",99999999999999999999999]]
.map(x => ({ type: x[0], value: x[1] }));
console.log(list);
Or, with some destructuring:
const obj = { car: 1, van: 5, cat: 99999999999999999999999 };
let list = Object.entries(obj)
.map(([type, value]) => ({ type, value }));
console.log(list);
The callback to map:
([type, value]) => ({ type, value })
Expects an array as parameter: [type, value]. The first value in that array is assigned to type, the second one to value.
Then we use a shorthand form to set these values in our returned object:
=> ({ type, value })
I'm a beginner. I tried to solve the problem and this is the best I can come up with, tested in Node.js 10.
const obj = {"car": 1, "van": 5, "cat": 999999}
const items = []
for (let key in obj) {
items.push({"type": key, "value": obj[key]})
}
console.log(items)
One thing I am slightly confused about is the difference between for..in vs for..of, I'm currently looking into it.
Object.keys will return:
['car', 'van', 'cat'];
On this array you can use Array's map function which creates a new array with the results of calling a provided function on every element in the calling array.
var a = {
car: 1,
van: 5,
cat: 99999999999999999999999
}
m = Object.keys(a).map((v)=>{
return {
type: v,
value: a[v]
}
})
console.log(m);
#GustavMahler hope you understand. To learn more about array functions you should look map, reduce and filter.
This one uses object.keys
let js = {car:1, van:5, cat:9999}
Object.keys(js).map( x => ({type: x, value: js[x] }) )
[ { type: 'car', value: 1 },
{ type: 'van', value: 5 },
{ type: 'cat', value: 9999 } ]

Programmatically modifying maps based on inner condition

I have a map like this for example
const Map = new Map().set('123', [ [ 'foo', 'bar' ] ]).set('456', [ [ 'baz', 'qux' ], [ 'quux', 'corge' ] ]);
/*
The structure of the Map looks like this:
Map {
'123' => [ [ 'foo', 'bar' ] ],
'456' => [ [ 'baz', 'qux' ], [ 'quux', 'corge' ] ]
}
*/
How would I go about deleting the array where the first nested element in the array === 'quux' so that it would return this?
Map {
'123' => [ [ 'foo', 'bar' ] ],
'456' => [ [ 'baz', 'qux' ] ]
}
I know how to remove the item by doing
Map.set('456', (Map.get('456')).filter(array => array[0] !== 'quux'));
But this is only because I know which key ('456') has the element with 'quux' in it. I'm not sure how I would programmatically sweep through the Map then find the corresponding key and then remove the item. The keys and values in the Map will dynamic (but the structure will be the same), whereas the element to search for will be static, i.e: 'quux', what I mean by this is that the contents in the Map could vary, and I am simply performing a search and remove.
You could iterate the map and if the wanted value is found, filter the array and assign the filtered array.
const map = new Map([['123', [['foo', 'bar']]], ['456', [['baz', 'qux'], ['quux', 'corge']]]]);
map.forEach((v, k, m) => {
if (v.some(a => a[0] === 'quux')) {
m.set(k, v.filter(a => a[0] !== 'quux'));
}
});
console.log([...map]);
You can loop over the values of the Map, use findIndex on each value v to see if it includes an array whose first element is quux, and splice that array out if so:
const map = new Map().set('123', [ [ 'foo', 'bar' ] ]).set('456', [ [ 'baz', 'qux' ], [ 'quux', 'corge' ] ]);
console.log("before", [...map]);
for (const v of map.values()) {
const index = v.findIndex((a) => a[0] === "quux");
if (index > -1) {
v.splice(index, 1);
}
}
console.log("after", [...map]);
Here’s the non-destructive alternative, which creates a new Map by taking the entries of the old one and mapping the values to filter out the arrays we don’t want:
const before = new Map().set('123', [ [ 'foo', 'bar' ] ]).set('456', [ [ 'baz', 'qux' ], [ 'quux', 'corge' ] ]);
console.log("before", [...before]);
const after = new Map([...before].map(([k, v]) => {
return [k, v.filter((a) => a[0] !== "quux")];
}))
console.log("after", [...after]);
NOTE: One difference between the two approaches is that the second one will remove all arrays that have quux as their first element, whereas the second one will remove only the first such array. They can, of course, both be altered to fit whichever of the two options you need.
You could do the key dynamically with a for of loop like this:
BTW open your devtools to checkout the new map since map cannot be properly displayed in the code snippet.
const Map = new Map().set('123', [
['foo', 'bar']
]).set('456', [
['baz', 'qux'],
['quux', 'corge']
]);
for (let el of Map) {
Map.set(el[0], (Map.get(el[0])).filter(array => array[0] !== 'quux'));
}
console.log(Map);
I hope this is what you wanted and otherwise you can comment and I will have a look at it ;).
Iterate over key-value pair of the map, the value will have the outer array from which we can filter out the inner array having the value we are looking for. We can get the index of the inner array from the forEach function, using which we can use the splice function to remove the inner array from the outer array.
const map = new Map().set('123', [ [ 'foo', 'bar' ] ]).set('456', [ [ 'baz', 'qux' ], [ 'quux', 'corge' ] ]);
map.forEach((v, k)=>
{
v.forEach((arr, idx)=> {
if(arr.includes('quux')){
v.splice(idx,1);
}
},)
});
console.log(map);
Not sure if it's better from the performance point to always use Array.prototype.filter or use Array.prototype.some before filtering the array.
This solution just filters all arrays without checking an apperance of 'quux' before.
const map = new Map().set('123', [ ['foo', 'bar' ] ]).set('456', [ [ 'baz', 'qux' ], [ 'quux', 'corge' ] ]);
map.forEach((val, key) => {
val = val.filter(arr => arr[0] !== 'quux');
map.set(key, val);
});
console.log(map);

Categories