Javascript can't group objects with 2 values - javascript

I have this object data:
[ RowDataPacket {
id: 59,
steamid: '76561198220437096',
product_id: 23,
status: 1,
date: 2017-12-18T17:27:19.000Z,
message: null,
name: 'CS.MONEY',
amount: 100,
website: 'csgo500' },
RowDataPacket {
id: 60,
steamid: '76561198220437096',
product_id: 24,
status: 1,
date: 2017-12-18T17:27:19.000Z,
message: null,
name: 'CS.MONEY',
amount: 250,
website: 'csgo500' },
RowDataPacket {
id: 61,
steamid: '76561198220437096',
product_id: 23,
status: 1,
date: 2017-12-18T17:27:19.000Z,
message: null,
name: 'CS.MONEY',
amount: 100,
website: 'csgo500' },
RowDataPacket {
id: 62,
steamid: '76561198345348530',
product_id: 6,
status: 1,
date: 2017-12-18T20:05:55.000Z,
message: null,
name: 'wal gruche',
amount: 100,
website: 'csgoatse' }
Im trying to sort this data with steamid and website, i managed to sort this only by one value like this:
var groupedOrders = {};
row.forEach(function(item){
var list = groupedOrders[item.steamid];
if(list){
list.push(item);
} else{
groupedOrders[item.steamid] = [item];
}
});
My idea was to make two dimensional array but for some reason i cant do it like this:
var list = groupedOrders[item.steamid][item.website];
It throws me an error "Cant read property ... of undefined"
Now my code looks like this:
var groupedOrders = {};
row.forEach(function(item){
var list = groupedOrders[item.steamid][item.website];
if(list){
list.push(item);
} else{
groupedOrders[item.steamid][item.website] = [item];
}
});
Do you have any ideas how to fix this errors?

The problem is that var list = groupedOrders[item.steamid][item.website] is actually saying:
var temp = groupedOrders[item.steamid];
var list = temp[item.website];
There is no entry at groupedOrders[item.steamid] and so line one sets temp to undefined. The second line tries to index into undefined which is an error.
You would have to split the code out and essentially do the whole one-key grouping twice:
var outerList = groupedOrders[item.steamid];
if (!outerList)
outerList = groupedOrders[item.steamid] = {};
var innerList = outerList[item.website];
if (innerList)
innerList.push(item);
else
outerList[item.website] = [item];
(I have not tested this code but it is the right shape.)

The following works by creating a recursive groupBy grouping function for each of the fields supplied as an argument.
These dynamically created groupBy functions are then invoked one by one, passing the result between, starting with the supplied data.
Each groupBy function instance creates an object and adds properties to it corresponding to the key values for the field being grouped.
By calling these groupBy functions successively, we create a progressively more nested tree of objects, with groups at each successive level marked as being groups using a symbol.
The final result is a nest (a tree!) of objects, with keys corresponding to the field used for indexing at that level.
Finally, we flatten the nest and the final order is visible.
const flatten = o => Object.values(o).reduce((acc, c) => (Array.isArray(c) ? [...acc, ...c] : typeof c === 'object' ? [...acc, ...flatten(c)] : [...acc, c]), []);
const flow = (...fns) => data => fns.reduce((acc, c) => c(acc), data);
const GROUP = Symbol('group');
const asGroup = (result = []) => ((result[GROUP] = true), result);
const isGroup = o => o[GROUP];
const groupBy = field => (data, key) =>
data.reduce((acc, c) =>
((key = c[field]), (acc[key] ?
(acc[key].push(c), acc) :
((acc[key] = asGroup([c])), acc))), {});
const recurse = (test) => (transform) => o =>
test(o)
? transform(o)
: Object.entries(o).reduce(
(acc, [k, v]) => (test(v) ?
((acc[k] = transform(v)), acc) :
((acc[k] = recurse(test)(transform)(v)), acc)), {});
const group = (...fields) => flow(...fields.map(flow(groupBy, recurse(isGroup))), flatten);
const rows = asGroup([
{
id: 0,
steamid: '2',
website: 'a'
},
{
id: 1,
steamid: '2',
website: 'b'
},
{
id: 2,
steamid: '2',
website: 'a'
},
{
id: 3,
steamid: '1',
website: 'b'
},
{
id: 4,
steamid: '0',
website: 'b'
}
]);
console.log(JSON.stringify(group('steamid', 'website')(rows), null, 2));

Related

How to combine all objects into one based on key

Here is the scenario I am looking at:
I want to reduce these objects
const data = [
{
id: 1,
totalAmount: 1500,
date: '2021-01-01',
vendor_id: 2,
totalTransaction: 12,
isRefund: false,
},
{
id: 2,
totalAmount: 200,
date: '2021-01-01',
vendor_id: 2,
totalTransaction: 2,
isRefund: true,
},
{
id: 3,
totalAmount: 200,
date: '2021-01-01',
vendor_id: 2,
totalTransaction: 1,
isRefund: true,
},
];
and I found a solution that adds their values:
const deepMergeSum = (obj1, obj2) => {
return Object.keys(obj1).reduce((acc, key) => {
if (typeof obj2[key] === 'object') {
acc[key] = deepMergeSum(obj1[key], obj2[key]);
} else if (obj2.hasOwnProperty(key) && !isNaN(parseFloat(obj2[key]))) {
acc[key] = obj1[key] + obj2[key]
}
return acc;
}, {});
};
const result = data.reduce((acc, obj) => (acc = deepMergeSum(acc, obj)));
const array = []
const newArray = [...array, result]
which results to:
const newArray = [
{
id: 6,
totalAmount: 1900,
date: '2021-01-012021-01-012021-01-01',
vendor_id: 6,
totalTransaction: 15
}
]
And now my problem is I don't know yet how to work this around to have my expected output which if isRefund is true, it must be subtracted instead of being added, retain the vendor_id and also the concatenated date instead of only one entry date:
const newArray = [
{
id: 1(generate new id if possible),
totalAmount: 1100,
date: '2021-01-01',
vendor_id: 2,
totalTransaction: 15,
isRefund: null(this can be removed if not applicable),
},
];
I will accept and try to understand any better way or workaround for this. Thank you very much.
As you want custom behaviour for several fields, and don't need the recursive aspect of the merge, I would suggest you create a custom merge function, specific to your business logic:
const data = [{id: 1,totalAmount: 1500,date: '2021-01-01',vendor_id: 2,totalTransaction: 12,isRefund: false,},{id: 2,totalAmount: 200,date: '2021-01-01',vendor_id: 2,totalTransaction: 2,isRefund: true,},{id: 3,totalAmount: 200,date: '2021-01-01',vendor_id: 2,totalTransaction: 1,isRefund: true,},];
function customMerge(a, b) {
if (a.vendor_id !== b.vendor_id || a.date !== b.date) {
throw "Both date and vendor_id must be the same";
}
return {
id: Math.max(a.id, b.id),
totalAmount: (a.isRefund ? -a.totalAmount : a.totalAmount)
+ (b.isRefund ? -b.totalAmount : b.totalAmount),
date: a.date,
vendor_id: a.vendor_id,
totalTransaction: a.totalTransaction + b.totalTransaction
};
}
const result = data.reduce(customMerge);
if (data.length > 1) result.id++; // Make id unique
console.log(result);
You could also reintroduce the isRefund property in the result for when the total amount turns out to be negative (only do this when data.length > 1 as otherwise result is just the original single object in data):
result.isRefund = result.totalAmount < 0;
result.totalAmount = Math.abs(result.totalAmount);
Distinct results for different dates and/or vendors
Then use a "dictionary" (plain object or Map) keyed by date/vendor combinations, each having an aggregating object as value.
To demonstrate, I added one more object in the data that has a different date and amount of 300:
const data = [{id: 1,totalAmount: 1500,date: '2021-01-01',vendor_id: 2,totalTransaction: 12,isRefund: false,},{id: 2,totalAmount: 200,date: '2021-01-01',vendor_id: 2,totalTransaction: 2,isRefund: true,},{id: 3,totalAmount: 200,date: '2021-01-01',vendor_id: 2,totalTransaction: 1,isRefund: true,},{id: 4,totalAmount: 300,date: '2021-01-02',vendor_id: 2,totalTransaction: 1,isRefund: false,}];
function customMerge(acc, {id, date, vendor_id, totalAmount, totalTransaction, isRefund}) {
let key = date + "_" + vendor_id;
if (!(id <= acc.id)) acc.id = id;
acc[key] ??= {
date,
vendor_id,
totalAmount: 0,
totalTransaction: 0
};
acc[key].totalAmount += isRefund ? -totalAmount : totalAmount;
acc[key].totalTransaction += totalTransaction;
return acc;
}
let {id, ...grouped} = data.reduce(customMerge, {});
let result = Object.values(grouped).map(item => Object.assign(item, { id: ++id }));
console.log(result);
it could be help, if you are looking for the same output, can add other checklist based on your requirement, for filtered date, logic would be little different but the output will be same.
const getTotalTranscation = () =>
transctionLists.reduce((prev, current) => {
const totalAmount = current.isRefund
? prev.totalAmount - current.totalAmount
: prev.totalAmount + current.totalAmount;
const totalTransaction = current.isRefund
? prev.totalTransaction - current.totalTransaction
: prev.totalTransaction + current.totalTransaction;
return {
...current,
id: current.id + 1,
totalAmount,
totalTransaction,
isRefund: totalAmount < 0,
};
});

How to update the value into a nested array of objects if value for specific key is same?

let data = [
{"name":"Dhanush","createdAt":"2021/01/13 16:57:53","songs":[]},
{"name":"Dharma","createdAt":"2021/01/13 17:02:47","songs":[]},
{"name":"Sachin","createdAt":"2021/01/13 17:30:45","songs":[]}
]
let name = "Dhanush"
let song = {
'id':1,
'duration': '5 mins',
'name': 'Bingo'
}
Here I need to loop the data array and check if data.name === name , if it s true I need to push the song object to the songs array inside the data.
this means
data = data.map(val => val.name === name ? val.songs = [...val.songs,song] : val.songs)
I tried like this. but it doesn't work.
Your help is much appreciated.
Thanks
You are not returning val when using map method.
let data = [
{ name: 'Dhanush', createdAt: '2021/01/13 16:57:53', songs: [] },
{ name: 'Dharma', createdAt: '2021/01/13 17:02:47', songs: [] },
{ name: 'Sachin', createdAt: '2021/01/13 17:30:45', songs: [] },
];
const name = 'Dhanush';
const song = {
id: 1,
duration: '5 mins',
name: 'Bingo',
};
data = data.map(
(val) => (
val.name === name ? (val.songs = [...val.songs, song]) : val.songs, val
)
);
console.log(data);

Joining two arrays of JSON objects like an SQL join using functional programming

Consider, I have the following two arrays of objects:
const existingAndArchivedBookings =
[
{"booking_id":-2},
{"booking_id":-1},
{"booking_id":999}
]
const newAndExistingBookings =
[
{bookingId:-2, name: "name1"},
{bookingId:-3, name: "name1"},
{bookingId:-1, name: "namex"}
]
What I want to do is determine which of the bookings in the second array are new and which are existing. Any bookingId that is in both arrays is existing. Any bookingID that is in the second array but not the first is new. So, the result of the solution should be an array as follows:
[ { bookingId: -2, existing: true, name: 'name1' },
{ bookingId: -3, existing: false, name: 'name1' },
{ bookingId: -1, existing: true, name: 'namex' } ]
I have a solution (which I'll post as an answer), but I think there's probably a more efficient way of doing it. Good luck.
If you want a non-R answer: you can use a simple map to iterate over the data, compare the booking ids in both arrays (with some), and return a new array of objects.
const existingAndArchivedBookings = [{booking_id:-2},{booking_id:-1},{booking_id:999}];
const newAndExistingBookings = [{bookingId:-2, name: "name1"},{bookingId:-3, name: "name1"},{bookingId:-1, name: "namex"}];
function testBookings(arr1, arr2) {
return arr2.map(({ bookingId, name }) => {
const existing = arr1.some(obj => obj.booking_id === bookingId);
return { bookingId, existing, name };
});
}
const out = testBookings(existingAndArchivedBookings, newAndExistingBookings);
console.log(out);
You can greatly simplify it using Array.prototype.reduce to form the result of the comparisons between the 2 arrays and Array.prototype.findIndex to test whether the object in the second array is present in the first array:
const existingAndArchivedBookings =
[
{"booking_id":-2},
{"booking_id":-1},
{"booking_id":999}
]
const newAndExistingBookings =
[
{bookingId:-2, name: "name1"},
{bookingId:-3, name: "name1"},
{bookingId:-1, name: "namex"}
]
const res = newAndExistingBookings.reduce((acc, ele) => {
const idx = existingAndArchivedBookings.findIndex(b => b.booking_id === ele.bookingId);
let existing = false;
if(idx >=0 ){
existing = true;
}
return acc.concat({bookingId : `${ele.bookingId}`, existing: `${existing}`, name: `${ele.name}`});
}, []);
console.log(res);
Here's what I came up with, which seems a bit long winded
const R = require('ramda')
const existingAndArchivedBookings = [{"booking_id":-2},{"booking_id":-1},{"booking_id":999}]
const newAndExistingBookings = [{bookingId:-2, name: "name1"}, {bookingId:-3, name: "name1"}, {bookingId:-1, name: "namex"}]
const existingAndArchivedKeys = existingAndArchivedBookings.map(value => value.booking_id)
const newAndExistingKeys = newAndExistingBookings.map(value => value.bookingId)
const existingKeys = existingAndArchivedKeys.filter(key => newAndExistingKeys.includes(key))
const newKeys = newAndExistingKeys.filter(key => !existingAndArchivedKeys.includes(key))
const existingBookingIds = existingKeys.map(key => {
return {bookingId: key, existing: true}
})
const newBookingIds = newKeys.map(key => {
return {bookingId: key, existing: false}
})
const allArray = R.concat(newAndExistingBookings, R.concat(existingBookingIds, newBookingIds))
console.log(R.values(R.reduceBy(R.mergeLeft, {}, R.prop('bookingId'), allArray)))

Javascript concat and prefix or rename props

I have two arrays that I am concatinating.
However each of these arrays has same property name I want to leave by adding prefix to each.
Array A(aData) looks like
[
{
id: 1,
title: `title`
code: '34x'
},
...
]
Array B(bData) looke like:
[
{
id: 1
prop: 3,
otherporp: `prop`
code: 'hi67'
},
...
]
In order to combine the arrays I am doing concat and reduce to get only matching id's
const data: any = aData.concat(bData).reduce((acc, x) => {
acc[x.id] = Object.assign(acc[x.id] || {}, x);
return acc;
}, {});
return Object.values(data);
But the issue is that my bData code props getting lost.
Is there any way I can rename the code from aData to say aCode and the code from bData to bCode ?
You can create a new array from both of your array with updated key value aCode and bCode instead of code key. Then concat both of these arrays and merge them on the id key.
const arrA = [{ id: 1, title: `title`, code: '34x' }],
arrB = [{ id: 1, prop: 3, otherporp: `prop`, code: 'hi67'}];
const newArrA = arrA.map(({code, ...rest}) => ({...rest, aCode : code}));
const newArrB = arrB.map(({code, ...rest}) => ({...rest, bCode : code}))
const merged = Object.values([].concat(newArrA, newArrB).reduce((r,o) => {
r[o.id] = r[o.id] || Object.assign({},o);
Object.assign(r[o.id], o);
return r;
}, {}));
console.log(merged);
var arrA = [{
id: 1,
title: `title`,
code: '34x'
}],
arrB = [{
id: 1,
prop: 3,
otherporp: `prop`,
code: 'hi67'
}];
let newArrA = arrA.map(({
code,
...rest
}) => ({ ...rest,
aCode: code
}));
const newArrB = arrB.map(({
code,
...rest
}) => ({ ...rest,
bCode: code
}));
result = newArrA.map(function(v) {
var ret;
$.each(newArrB, function(k, v2) {
if (v2.id === v.id) {
ret = $.extend({}, v2, v);
return false;
}
});
return ret;
});
console.log(result);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

How can I get a unique array based on object property using underscore

I have an array of objects and I want to get a new array from it that is unique based only on a single property, is there a simple way to achieve this?
Eg.
[ { id: 1, name: 'bob' }, { id: 1, name: 'bill' }, { id: 1, name: 'bill' } ]
Would result in 2 objects with name = bill removed once.
Use the uniq function
var destArray = _.uniq(sourceArray, function(x){
return x.name;
});
or single-line version
var destArray = _.uniq(sourceArray, x => x.name);
From the docs:
Produces a duplicate-free version of the array, using === to test object equality. If you know in advance that the array is sorted, passing true for isSorted will run a much faster algorithm. If you want to compute unique items based on a transformation, pass an iterator function.
In the above example, the function uses the objects name in order to determine uniqueness.
If you prefer to do things yourself without Lodash, and without getting verbose, try this uniq filter with optional uniq by property:
const uniqFilterAccordingToProp = function (prop) {
if (prop)
return (ele, i, arr) => arr.map(ele => ele[prop]).indexOf(ele[prop]) === i
else
return (ele, i, arr) => arr.indexOf(ele) === i
}
Then, use it like this:
const obj = [ { id: 1, name: 'bob' }, { id: 1, name: 'bill' }, { id: 1, name: 'bill' } ]
obj.filter(uniqFilterAccordingToProp('abc'))
Or for plain arrays, just omit the parameter, while remembering to invoke:
[1,1,2].filter(uniqFilterAccordingToProp())
If you want to check all the properties then
lodash 4 comes with _.uniqWith(sourceArray, _.isEqual)
A better and quick approach
var table = [
{
a:1,
b:2
},
{
a:2,
b:3
},
{
a:1,
b:4
}
];
let result = [...new Set(table.map(item => item.a))];
document.write(JSON.stringify(result));
Found here
You can use the _.uniqBy function
var array = [ { id: 1, name: 'bob' }, { id: 2, name: 'bill' }, { id: 1, name: 'bill' },{ id: 2, name: 'bill' } ];
var filteredArray = _.uniqBy(array,function(x){ return x.id && x.name;});
console.log(filteredArray)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.js"></script>
In the above example, filtering is based on the uniqueness of combination of properties id & name.
if you have multiple properties for an object.
then to find unique array of objects based on specific properties, you could follow this method of combining properties inside _.uniqBy() method.
I was looking for a solution which didn't require a library, and put this together, so I thought I'd add it here. It may not be ideal, or working in all situations, but it's doing what I require, so could potentially help someone else:
const uniqueBy = (items, reducer, dupeCheck = [], currentResults = []) => {
if (!items || items.length === 0) return currentResults;
const thisValue = reducer(items[0]);
const resultsToPass = dupeCheck.indexOf(thisValue) === -1 ?
[...currentResults, items[0]] : currentResults;
return uniqueBy(
items.slice(1),
reducer,
[...dupeCheck, thisValue],
resultsToPass,
);
}
const testData = [
{text: 'hello', image: 'yes'},
{text: 'he'},
{text: 'hello'},
{text: 'hell'},
{text: 'hello'},
{text: 'hellop'},
];
const results = uniqueBy(
testData,
item => {
return item.text
},
)
console.dir(results)
In case you need pure JavaScript solution:
var uniqueProperties = {};
var notUniqueArray = [ { id: 1, name: 'bob' }, { id: 1, name: 'bill' }, { id: 1, name: 'bill' } ];
for(var object in notUniqueArray){
uniqueProperties[notUniqueArray[object]['name']] = notUniqueArray[object]['id'];
}
var uniqiueArray = [];
for(var uniqueName in uniqueProperties){
uniqiueArray.push(
{id:uniqueProperties[uniqueName],name:uniqueName});
}
//uniqiueArray
unique array by id property with ES6:
arr.filter((a, i) => arr.findIndex(b => b.id === a.id) === i); // unique by id
replace b.id === a.id with the relevant comparison for your case

Categories

Resources