Javascript - Add new object into array of objects - javascript

please. I have a cycle with fiance balances. It's an array of objects like:
export const balances = [
type: types.outgoing,
date: 20220410,
amount: 282.12,
category: categories.installments,
source: 'Debit account',
destination: 'Leasing company',
},
{
type: types.income,
date: 20220413,
amount: 1385.3,
category: categories.job,
source: 'My employeer',
destination: 'Debit account',
},
...
]
etc...
As you can see, I have a categories there which means that I have in cycle every transaction in balances and I must create separate category for each of them with total amount for each category, count of items in category and with detailed transactions for each category. I'm using array.forEach() cycle:
balances.forEach((balance) => {
// Checking if category already exists in my array of categories
let categoryIndex = categories.findIndex((category) => category.category === balance.category)
// Create details of transaction
let transactionDetail = {
date: balance.date,
amount: balance.amount,
source: balance.source,
destination: balance.destination,
}
// If category already exists, just calculate total and add new object into array details
if (categoryIndex !== -1) {
console.log([categories[categoryIndex].details])
categories[categoryIndex] = {
type: balance.type,
category: balance.category,
amount: categories[categoryIndex].amount + balance.amount,
count: (categories[categoryIndex].count += 1),
// This row is wrong. I cannot use this
details: [categories[categoryIndex].details].push(transactionDetail),
}
} else {
// If category doesn't yet exists, we must create a first values in this category
categories.push({
type: balance.type,
category: balance.category,
amount: balance.amount,
count: 1,
details: [transactionDetail],
})
}
}
But the row
details: [categories[categoryIndex].details].push(transactionDetail)
doesn't work properly. Probably the reason is, that I have sometimes Object as tyopeof result and sometimes undefined
Row console.log([categories[categoryIndex].details]) sometimes output:
// Output for
// console.log([categories[categoryIndex].details])
[Array(1)]0: Array(1)
0: {date: 20220414, amount: 410, source: 'xxx', destination: 'yyy'}
length: 1[[Prototype]]:
Array(0)length: 1
[[Prototype]]: Array(0)
[2]
0: 2
length: 1
[[Prototype]]: Array(0)
Any hiths how can add object transactionDetail as a next in existing array? Thank you very much for any advice.
I don't understand. I can concat string if category already exists, add numbers but I cannot add an next object into array of objects.
EDIT: Just changed transaction to trasactionDetail in explanation.

I found several errors in your latter block and have corrected them; let me explain the changes I've made.
In the line you marked as wrong, you were putting brackets around the values for some reason. Categories.details is presumably an array of TransactionDetail, so you don't need to further nest it here. However, if you push into an array, that returns with the number of objects in the array, so when you did this in that line, details would ultimately always be populated with a number. Rather, in my version, I split out the existing category you pulled via index as existing and simply push the value to its details array. This also just cleans up the top half of your condition since one need only reference the properties from the existing object to match against the new values in a cleaner way.
You were using 1(categories[categoryIndex].count += 1) to increase the count. But, you're also setting precisely that object here, so this isn't a good practice. Rather, set the values you intend to use here and commit it all to the categories array as one thing, instead of a mismatch of some values setting here, some set differently. I corrected this to a mere existing.count + 1.
Here's your updated code in full then:
balances.forEach((balance) => {
// Checking if category already exists in my array of categories
let categoryIndex = categories.findIndex(category => category.category === balance.category);
// Create details of transaction
let transactionDetail = {
date: balance.date,
amount: balance.amount,
source: balance.source,
destination: balance.destination,
};
// If category already exists, just calculate total and add new object into array details
if (categoryIndex !== -1) {
const existing = categories[categoryIndex];
existing.details.push(transactionDetail);
categories[categoryIndex] = {
type: balance.type,
category: balance.category,
amount: existing.amount + balance.amount,
count: existing.count + 1,
details: existing.details
};
} else {
// If category doesn't yet exists, we must create a first values in this category
categories.push({
type: balance.type,
category: balance.category,
amount: balance.amount,
count: 1,
details: [transactionDetail],
});
}
});

Related

Trying to iterate through an object's values and insert into new object (JS)

I am trying to create a series of new objects using the values from an existing object to push to my database.
Here is the existing object:
{
name: 'Pasta',
method: 'Cook pasta',
ingredients: [
{ measure: 'tbsp', quantity: '1', ingredient_name: 'lemon' },
{ measure: 'g', quantity: '1', ingredient_name: 'salt' },
{ measure: 'packet', quantity: '1', ingredient_name: 'spaghetti' },
{ measure: 'litre', quantity: '1', ingredient_name: 'water' }
]
}
Basically I have a function that inserts and returns the id of the recipe into one table, then inserts and returns/or finds the ids of the relevant ingredients and the final part (with which I am struggling) is to combine the returned recipe_id, ingredient_id and the correct measure and quantity (as written in the object above).
Here is where I have gotten to:
//starting point is here
async function addNewRecipe(newRecipe, db = connection) {
console.log(newRecipe)
const recipeDetails = {
recipe_name: newRecipe.name,
recipe_method: newRecipe.method,
}
const ingredientsArray = newRecipe.ingredients
const [{ id: recipeId }] = await db('recipes')
.insert(recipeDetails)
.returning('id')
const ingredientsWithIds = await getIngredients(ingredientsArray) //returns an array of ids
ingredientsWithIds.forEach((ingredientId) => {
let ingredientRecipeObj = {
recipe_id: recipeId, //works
ingredient_id: ingredientId, //works
measure: newRecipe.ingredients.measure, //not working - not sure how to match it with the relevant property in the newRecipe object above.
quantity: newRecipe.ingredients.quantity,//not working - not sure how to match it with the relevant property in the newRecipe object above.
}
//this is where the db insertion will occur
})
}
The desired output would be:
ingredientRecipeObj = {
recipe_id: 1
ingredient_id: 1
measure: tbsp
quantity: 1
} then insert this into db
followed by:
ingredientRecipeObj = {
recipe_id: 1
ingredient_id: 2
measure: g
quantity: 1
} then insert into db
etc. etc.
The problem seems to be that the function "getIngredients" returns only the IDs. Once you have fetched them, you have no way of knowing which ID is for which ingredient. One way to change that is to make the method return an array of both the ID and the ingredient name. Then you could match them like this:
const ingredientsWithIds = await getIngredients(ingredientsArray) //now an array of objects with ingredient_name and id
ingredientsWithIds.forEach((ingredient) => {
const recipeIngredient = ingredientsArray.find(ri => ri.ingredient_name === ingredient.ingredient_name)
const ingredientRecipeObj = {
recipe_id: recipeId,
ingredient_id: ingredient.id,
measure: recipeIngredient.measure,
quantity: recipeIngredient.quantity,
}
//this is where the db insertion will occur
})
Since you haven't posted the "getIngredients" function it is hard to say exactly how to adapt it to return the name as well.

Merging objects from different arrays

I am working on something where I take data from 2 different APIs that I have no control of and I want to combine the results in the most efficient way.
One of the arrays hold some assets, lets say books, the other one holds a transaction for the said book. Here is an example:
{
author: {name: 'J.K. Rowling', },
assetName: 'Book1'
}]
const array2 = [
{from: 'John',
to: 'Sarah,
price: 10,
timeStamp: 123,
assetName: 'Book1',
authorName: 'J.K. Rowling'
}]
Note that to find the corresponding transaction for a given book, you need both assetName and authorName to match - you can own more than one book of the same author and you can own two books with the same name but a different author but an author has only one book with a given name, thus finding the corresponding transaction to an asset requires both fields to match and there are no other unique identifiers.
The naive approach is to iterate over one of the arrays and for each entry to check in the second array to find the transaction but that looks like it will take too long to execute if the arrays are of substantial size.
I was wondering what better solutions can you think of for merging two objects with different structure that is efficient?
Well, if author.name + assetName form an id, you could iterate over array1 once & create a Map with keys being author.name + assetName & vales being original objects.
Then you could iterate over array2 once as well & enrich it whatever way you want. All lookups in the second iteration will be fast since you will access the Map instead of searching in array.
const indexedArray1 = new Map();
array1.forEach(data => indexedArray1.set(data.author.name + data.assetName, data);
const enrichedArray2 = array2.map(transaction => {
const relatedBook = indexedArray1.get(transaction.authorName + transaction.assetName);
// Merge relatedBook & transaction the way you want here
});
I often do the following when merging arrays
The time complexity is O(n)
const array1 = [{
author: {name: 'J.K. Rowling' },
assetName: 'Book1'
}]
const array2 = [{
from: 'John',
to: 'Sarah',
price: 10,
timeStamp: 123,
assetName: 'Book1',
authorName: 'J.K. Rowling'
}]
const array2_map = {}
array2.forEach(e => {
const key = `${e.assetName}:${e.authorName}`
if (!array2_map[key]) array2_map[key] = []
const { from, to, price, timeStamp } = e
array2_map[key].push({
from,
to,
price,
timeStamp
})
})
const merged_array = array1.map(e => ({
...e,
transaction: array2_map[`${e.assetName}:${e.authorName}`] || []
}))

How to compare and organise object data in 3 separate arrays whilst removing duplicates

I have 3 different arrays which contain an object with public holiday data (based on the region in the world e.g Mexico, Canada, US). I want to organise the data (store a combination of it all in its own array) so that if a regions share the same public holiday for example name: 'Christmas Day' date: 2021-12-25 then it only stores this once rather than for each region which is what its doing at the moment. The main issue is theres a problem with a lot of duplicate data inside allPublicHolidays function & i'm getting lost when it comes to the checkIfSame forEach loop as there is probably a much nicer way to do this.
The array data looks like this per region:
[
{
date: '2021-05-31 00:00:00',
name: 'Spring bank holiday',
},
{
date: '2021-12-25 00:00:00',
name: 'Christmas Day',
},
{
date: '2021-12-26 00:00:00',
name: 'Boxing Day',
}
]
The idea I had is to use a forEach loop for each region which pushes the data from each to its own array but checks the array each time it processes a holiday to make sure it doesn't already exist within the array. Before pushing it calls a function to check if the name data is the same as the array.
Here is what I have so far:
const regionUS = usArrayData;
const regionMX = mxArrayData;
const regionCA = caArrayData;
let allPublicHolidays = [];
function checkIfSame(name1, array) {
let boolean = false;
if (array) {
array.forEach((arr) => {
if (name1 == arr.name) {
boolean = true;
}
})
}
return boolean;
}
regionUS.forEach((USholiday) => {
if (!checkIfSame(USholiday.name, allPublicHolidays)) {
allPublicHolidays.push(USholiday);
}
});
regionMX.forEach((MXholiday) => {
if (!checkIfSame(MXholiday.name, allPublicHolidays)) {
allPublicHolidays.push(MXholiday);
}
});
Any help with storing all data in one array minus duplicates based on object.name && object.date would be really appreciated.
Thank you

How to assert types in an array of objects with Chai?

I have the following context:
const data = [
{
id: 1,
name: 'thenamefoo',
modified: new Date() // random date
},
{
id: 2,
name: 'namebar',
modified: new Date() // random date
},
...
];
expect(data)...
I want to assert that my data will always be an array that have objects with fixed keys (and types).
For instance, I want something like
expect(data)
.to.be.an('array')
.that.all.have.types.like({
id: Number,
name: String,
modified: Date
});
Is it possible? How? Any libs?
In my opinion you should focus on validating the data instead of toying with a clunky assertion DSL. With simple true/false checks, all you need is the humble assert:
test('my data is valid', () => {
data.forEach(({id, name, modified}) => {
assert(typeof id === 'number', `${id} is not a number`);
assert(typeof name === 'string', `${name} is not a string`);
assert(modified instanceof Date, `${modified} is not a date`);
});
});
Going further
This of course doesn't help much if you need to check other things:
The array is not empty
Each object has exactly id, name and modified as properties. No less no more
id is a positive integer
name is not an empty string
...
For more fined-grained control you should definitely look into jsonschema.

Accessing specific objects in an object of objects

I'm writing a program that takes a store inventory and searches for specific items in that inventory, pushing those items into an array. The inventory is all one object, and each object within this object is an item of the inventory. The items themselves have no keys- they're simply object literals. Therefore I'm stuck looping through them using fast enumeration (for item in products). Each item looks like this:
{ id: 2759167427,
title: 'Practical Silk Bag',
handle: 'practical-silk-bag',
vendor: 'Legros, Willms and Von',
product_type: 'Bag'
}
What I'm trying to do is push the item object to an array if and only if that item is either a keyboard or a computer. To that end I tried employing something like this :
var kbComps = [];
//loop through the inventory, looking for everything that is a keyboard or a computer
for (var key in products) {
var item = products[key];
for (var property in item) {
if (item[property].includes("Computer") || item[property].includes("Keyboard")) {
kbComps.push(item);
}
}
}
However I'm getting an error that tells me includes isn't a defined method, meaning the program isn't recognizing item[title] as a string, so now I'm stuck. How would I circumvent this? Any help is appreciated.
Cheers all
UPDATED
I changed the implementation to loop over an object and not an array. I think this is what you are looking for.
Here is a working jsBin
May be this is a little simpler and I'm sure it would work for you
// Products base data
var productsData = {
a: {
id: 2759167427,
title: 'Practical Silk Bag',
handle: 'practical-silk-bag',
vendor: 'Legros, Willms and Von',
product_type: 'Bag',
},
b: {
id: 2759167417,
title: 'Practical Silk Bag 2',
handle: 'practical-silk-bag-2',
vendor: 'Legros, Willms and Von 2',
product_type: 'Bag 2',
},
c: {
id: 2759167417,
title: 'Practical Silk Bag 3',
handle: 'practical-silk-bag-3',
vendor: 'Legros, Willms and Von 3',
product_type: 'Computer', // This product must be returned
},
d: {
id: 2759167417,
title: 'Practical Silk Bag 4',
handle: 'practical-silk-bag-4',
vendor: 'Legros, Willms and Von 4',
product_type: 'Keyboard', // This product must be returned
}
};
/**
* Function to find products by any condition
*/
function searchItemsByCondition(products, evaluateCondition) {
var ans = [];
for (var item in productsData) {
// Making sure the object has the property product_type
if (products[item].hasOwnProperty('product_type')) {
if (evaluateCondition(products[item])) {
ans.push(products[item]);
}
}
}
return ans;
}
function searchByKeyboardOrComputer(product) {
return (product.product_type === 'Computer') || (product.product_type === 'Keyboard');
}
// Call the function passing the evaluation function to satisfy.
// It should log only the items with 'Keyboard' or 'Computer' product_type
console.log(searchItemsByCondition(productsData, searchByKeyboardOrComputer));
Hope this works for you!
In the first iteration of your loop, you're checking if id contains a string but id is a number therefore .includes fails.
I'm not sure what you're intention is but you might want to only check .includes if the item is a string.
if (typeof item[property] === 'string' && (item[property].includes("Computer") || item[property].includes("Keyboard"))) {
If you throw some console logs in you can see what's going on. https://jsfiddle.net/qexssczd/1/

Categories