convert an array to an object of key value pairs - javascript

if I have an array of strings like:
['person,item,cost,amount',
'John,shoes,200,2']
how could I convert this into an object that resembles:
{
'John':[
{
item:'shoes',
cost:'200',
amount:'2',
totalPriceForItems:'400'
}

If I understand correctly, you may try something like this:
const convert = data => {
const [columnsText, ...items] = data;
const columns = columnsText.split(',');
return items.reduce((acc, text) => {
const { person, ...entries } = Object.fromEntries(text.split(',').map((value, i) => [columns[i], value]));
entries.totalPriceForItems = String(entries.cost * entries.amount);
if(acc[person]) {
acc[person].push(entries);
} else {
acc[person] = [entries];
}
return acc;
}, {});
};
const result = convert([
'person,item,cost,amount',
'John,shoes,200,2',
'Bob,glasses,50,3',
'John,shirts,100,5',
]);
console.log(result);

According to your comment,
I have 8 lines of 'John,shoes,200,2' but with different amounts in the
same array. The 'person,item,cost,amount' is only mentioned once at
the start of the array
What I understand is that you have a csv with headers and multiple rows.
If that is the case, then your data would resemble something like this:
data = [
'person,item,cost,amount',
'John,shoes,200,2',
'Adam,pants,60,1',
'Kelly,skirt,180,2',
'John,skirt,150,3'
]
Then you could consider the following approach, that is generic enough to adapt to different headers, and multiple data rows with repeated keys (person names).
// here, you define a function to transform each row of your data,
// like parsing numeric attributes and calculating the totals
function transform(row) {
row.cost = Number.parseInt(row.cost)
row.amount = Number.parseInt(row.amount)
row.total = row.cost * row.amount
return row
}
// The following logic is generic, and can be used
// to map and aggregate any kind of csv with headers
hdrs = data.shift().split(',').slice(1)
rows = data.map(r => r.split(',')).reduce((acc, [n, ...kvs]) =>
({ ...acc, [n]: [...acc[n] || [], transform(Object.fromEntries(kvs.map((v, i) => [hdrs[i], v])))] }), {})
Output:
{
John: [
{ item: "shoes", cost: 200, amount: 2, total: 400 },
{ item: "skirt", cost: 150, amount: 3, total: 450 }],
Adam: [
{ item: "pants", cost: 60, amount: 1, total: 60 }],
Kelly: [
{ item: "skirt", cost: 180, amount: 2, total: 360 }]
}

Related

Accessing the body of the response object

I am having what I think is a pretty trivial problem but somehow I can't find a solution to. I have a response body that looks like this:
{
"sizes": [
{
"43": 35
},
{
"42": 20
},
{
"38": 10
}
]
}
where the keys are shoe sizes and the value is quantity of each size. How do I access the sizes? What I currently have is this:
const sizesArray = response.data.sizes
const arr = Object.values(msizes);
console.log('arr', arr);
arr.map((i,a) => {
console.log('i',i);
console.log('a',a);
})
but i is then again a object {43: 35}
and a is just the index. I want somehow to assign the key to parameter called 'sizes' and the key to a parameter called quantity.
You can use Object.keys, a bit simpler than Object.entries
Example:
const data = { sizes: [{ "43": 35 }, { "42": 20 }, { "38": 10 }] };
const result = data.sizes.map((element, index) => {
let obj = Object.keys(element); // returns an array of keys
let key = obj[0]; // first element is the only key
let quantity = element[key]; // bracket notation, key is an string, not number
console.log("size", key);
console.log("quantity", quantity);
});
You can just iterate the sizes array, using reduce to append the keys of each object to an output array of sizes:
const data = { sizes: [{ "43": 35 }, { "42": 20 }, { "38": 10 }] }
const sizes = data.sizes.reduce((acc, s) => acc.concat(Object.keys(s)), [])
console.log(sizes)
If you want sizes and quantities, you can take a similar approach, just generate an object which accumulates both sets of values:
const data = { sizes: [{ "43": 35 }, { "42": 20 }, { "38": 10 }] }
const { sizes, quantities } = data.sizes
.reduce((acc, s) => {
acc.sizes = acc.sizes.concat(Object.keys(s))
acc.quantities = acc.quantities.concat(Object.values(s))
return acc
},
{ sizes : [], quantities : [] })
console.log(sizes)
console.log(quantities)
You were on the right track :)
Use Object.keys() to get an array of your keys (shoe-sizes). Then use the map()-function to create a new array. Use the index of map() to access the quantity in your response.
const sizesArray = response.data.sizes
const sizes = Object.keys(sizesArray);
const result = sizes.map((element, index) => ({
size: element,
quantity: sizesArray[index]
}));
console.log(result);

How to sum the array of object values and assigned them to the relevant key name

I need to understand the simplest way of doing this.
I've got an array of objects:
const data = [
{
group: 'A',
incomes: {
"2019-12": 100,
"2020-12": 200,
"2021-12": 15
}
},
{
group: 'B',
incomes: {
"2019-12": 25,
"2020-12": 50,
}
}
]
What I'm trying to get is simple object where its key is the month from data.incomes and the value is sum of relative month values, so the final result looks like:
const totalIncomes = {
"2019-12": 125,
"2020-12": 250,
"2021-12": 15
}
Can anybody explain it to me step by step, please?
solved using reduce and forEach
Inside the reduce function I'm running a forEach on the array of keys of the incomes object/attribute. For each key which is a date I'm checking if the accumulator of the reduce function contains an attribute for each date and creates if not. After creating the attribute I'm summing the value for the current date attribute.
const data = [{
group: 'A',
incomes: {
"2019-12": 100,
"2020-12": 200,
"2021-12": 15
}
},
{
group: 'B',
incomes: {
"2019-12": 25,
"2020-12": 50,
}
}
]
const totalIncomes = data.reduce((acc, curr) => {
Object.keys(curr.incomes).forEach((key, index) => {
if (!acc[key]) {
acc[key] = 0
}
acc[key] += curr.incomes[key]
})
return acc
}, {})
console.log(totalIncomes)
Maybe this is not the pretties solutions but you can do it like this, the function is of course not necessary.
const data = [
{
group: "A",
incomes: {
"2019-12": 100,
"2020-12": 200,
"2021-12": 15,
},
},
{
group: "B",
incomes: {
"2019-12": 25,
"2020-12": 50,
},
},
];
getterInformation(data);
function getterInformation(object) {
let objectWithCalculatedValues = {};
object.forEach((items) => {
for (const key in items.incomes) {
if (objectWithCalculatedValues[key] === undefined) {
objectWithCalculatedValues[key] = 0;
}
objectWithCalculatedValues[key] += items.incomes[key];
}
});
console.log(objectWithCalculatedValues);
}
Assuming that this information may be useful to readers who may be unable to obtain necessary guidance (due to various possible reasons), here is one possible way to achieve the objective (solution):
const aggregateIncomesByMonth = () => (
data.map(d => Object.entries(d.incomes).map(([k, v]) => ({
key: k,
value: v
}))).flat().reduce((fin, itm) => ({
...fin,
[itm.key]: (fin[itm.key] || 0) + itm.value
}), {})
);
Explanation
Extract only the incomes from the data array
For each income object, get the key-value pair and transform into another object of the structure {key: 20yy-mm, value: nn}
Use .flat() to transform the result from step-2 into a 1-dimensional array
Use .reduce to sum the value for those cases where the key (ie, 20yy-mm) matches.
Code-snippet
const data = [{
group: 'A',
incomes: {
"2019-12": 100,
"2020-12": 200,
"2021-12": 15
}
},
{
group: 'B',
incomes: {
"2019-12": 25,
"2020-12": 50,
}
}
];
const aggregateIncomesByMonth = () => (
data.map(d => Object.entries(d.incomes).map(([k, v]) => ({
key: k,
value: v
}))).flat().reduce((fin, itm) => ({
...fin,
[itm.key]: (fin[itm.key] || 0) + itm.value
}), {})
);
console.log(aggregateIncomesByMonth());
My approach here is to destructure the array. This way I have all the data of the incomes of group A in the variable A and the same for B.
Then I do a double loop to compare both objects data and see if the dates match. If so, sum the incomes and add the data to the total object.
const data = [
{
group: 'A',
incomes: { "2019-12": 100, "2020-12": 200, "2021-12": 15 }
},
{
group: 'B',
incomes: { "2019-12": 25, "2020-12": 50 }
}
]
let A, B, total = {};
[A, B] = [data[0].incomes, data[1].incomes]
for(const date in A){
for(const d in B){
total[date] = date === d ? A[date] + B[date] : A[date]
}
}
console.log(total)

How can I sort through an Axios response?

I am using Axios to execute a GET request to a public API, I need to combine the names if they are the same and add the values up to only show the top 20 (It's a large dataset) based on the highest to lowest amounts(ascending order).
Axios Response
[
{
name: "foo1",
value: "8123.30"
},
{
name: "foo1",
value: "2852.13"
},
{
name: "foo2",
value: "5132.23"
},
{
name: "foo1",
value: "1224.20"
},
{
name: "foo2",
value: "1285.23"
}
1200...
];
Expected Output
[
{ name: "foo1",
value: "12199.63" // from all combined "foo1" amounts in the dataset
},
{
name: "foo2",
value: "6417.46" // from all combined "foo2" amounts in the dataset
},
18..
]
I tried to do something like this....
const fetchData = () => {
return axios.get(url)
.then((response) => response.data)
};
function onlyWhatINeed() {
const newArr = []
return fetchData().then(data => {
const sortedData = data.sort((a, b) => parseFloat(a.value) - parseFloat(b.value));
// I need to loop through the dataset and add all the "values" up
// returning only the top 20 highest values in an array of those objects
newArr.push(sortedData)
})
}
But I am confused as to how to push this data to a new array of the sorted data (top 20 values in ascending order) and use this data in my web application. I am a bit new to creating REST APIs so if you could provide articles and/or resources so I can understand a little more that would be an awesome bonus!
You can combine the entries that share the same name using a map, then sort the map and keep the first twenty elements :
function onlyWhatINeed() {
const newArr = []
return fetchData().then(data => {
let map = new Map();
data.forEach(d => {
if(!map.has(d.name)) {
map.set(d.name, parseFloat(d.value));
} else {
map.set(d.name, map.get(d.name) + parseFloat(d.value));
}
})
return Array.from(map.entries()).sort((a, b) => a.value - b.value).slice(0, 20);
})
}
Since you're dealing with a large dataset, I recommend that you handle this server side instead of offloading the sorting to your clients.
async function fetchData(){
const { data } = await axios.get(url);
let newArr = []
data.forEach((e,i) => {
let index = newArr.findIndex(el => el.name === e.name);
if(index !== -1 ) newArr[index].value += parseFloat(e.value); //add to the value if an element is not unique
if(index === -1 ) newArr.push({...e, value: parseFloat(e.value)}); //push to the array if the element is unique and convert value to float
});
return newArr.sort((a,b) => a.value - b.value).slice(0,20);//returns an array of 20 elements after sorting
}
Please do more research on how to work with arrays and objects in general.
If you happen to already be using lodash, then here's a functional-style solution using lodash chaining. Probably not optimal performance, but could be useful for relatively small datasets.
const _ = require('lodash');
const data = [
{
name: "foo1",
value: "8123.30"
},
{
name: "foo1",
value: "2852.13"
},
{
name: "foo2",
value: "5132.23"
},
{
name: "foo1",
value: "1224.20"
},
{
name: "foo2",
value: "1285.23"
},
{
name: "foo3",
value: "1000.00"
},
{
name: "foo3",
value: "2000.00"
}
];
// 1. convert string values to floats
// 2. group by name
// 3. sum values by name
// 4. sort by descending value
// 5. take top 20
const output =
_(data)
.map(obj => ({
name: obj.name,
value: parseFloat(obj.value)
}))
.groupBy('name')
.map((objs, key) => ({
name: key,
value: _.sumBy(objs, 'value')
}))
.orderBy(['value'], 'desc')
.slice(0, 20)
.value();
console.log('output:', output);

Get array of all unique object values based on property name

How can I get an array with all the unique values based on a property name?
In my case my object looks like this and I want an array with the unique documentID's.
const file = {
invoice: {
invoiceID: 1,
documentID: 5
},
reminders: [
{
reminderID: 1,
documentID: 1
},
{
reminderID: 2,
documentID: 1
}
]
}
The result should be an array [5, 1] //The unique documentID's are 5 and 1
It doesn't seem like possible to add a property name to the Object.values() function.
You can use Set to get unique documentID.
const file = {
invoice: {
invoiceID: 1,
documentID: 5
},
reminders: [
{
reminderID: 1,
documentID: 1
},
{
reminderID: 2,
documentID: 1
}
],
payments: {
documentID : 5
}
};
var keys = Object.keys(file).map(key=>file[key].map ? file[key].map(i=>i.documentID) : file[key].documentID)
var keysFlattened= [].concat.apply([], keys);
var unique = new Set(keysFlattened);
console.log(Array.from(unique));
I use something like this that does what you want I think
const keepUniqueBy = key => (array, item) => {
if (array.find(i => item[key] === i[key])) {
return array;
} else {
return [ ...array, item ];
}
};
Then you can simply: const unique = reminders.reduce(keepUniqueBy('documentID'))
NB: It's probably low performing, but for small arrays it doesn't matter.

Javascript can't group objects with 2 values

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));

Categories

Resources