Accessing specific objects in an object of objects - javascript

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/

Related

Javascript - Add new object into array of objects

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],
});
}
});

Mapping an array of values to an array of objects

So I have an issue that requires me to map an array of names to an object with a property of name.
The following code is in question:
this.planNames.map((name,index) => {
this.tempdata[index].name = name;
}),
Here is an example of the array.
tempdata: any = [
{
name: 'Mix and match',
title: 'Single-line plans for voice messaging and data.',
desc:
'A great plan option for businesses that want each line on its own plan. These plans have no maximum line caps.',
},
{
name: 'Flexible',
title: 'Multi-line plans for when one size does not fit all.',
desc:
'A great plan option for up to 10 phones with different unlimited data needs',
}
Any idea using arrow functions would be great as I am learning more about these myself.
There should be some changes in your map function.
this.planNames.map((item,index) => {
this.tempdata[index].name = item.name;
});
console.log(this.tempData)
[].map returns a new array containing the result of each of the original array elements passed through the callback function. So, you don't need to create a temporary array beforehand - the result of the map function will contain what you need.
const newTempData = this.planNames.map((name, index) => {
return { ...tempdata[i], name }
});
You don't generally mutate data in a .map function. If you're interested in mutating the original data structure, tempdata, you should just use a standard [].forEach.
tempdata.forEach((data, index) => {
data.name = this.planNames[index];
});
The map function is only to replace data by others in array.
If you want to update other array data, you should use a for loop:
const tempdata = [
{
name: 'Mix and match',
title: 'Single-line plans for voice messaging and data.',
desc:
'A great plan option for businesses that want each line on its own plan. These plans have no maximum line caps.',
},
{
name: 'Flexible',
title: 'Multi-line plans for when one size does not fit all.',
desc:
'A great plan option for up to 10 phones with different unlimited data needs',
}
];
const planNames = ['a', 'b'];
for(let i = 0; i < planNames.length; i++) {
tempdata[i].name = planNames[i];
}
console.log(tempdata);

Chai - Testing for values in array of objects

I am setting up my tests for the results to a REST endpoint that returns me an array of Mongo database objects.
[{_id: 5, title: 'Blah', owner: 'Ted', description: 'something'...},
{_id: 70, title: 'GGG', owner: 'Ted', description: 'something'...}...]
What I want my tests to verify is that in the return array it conatins the specific titles that should return. Nothing I do using Chai/Chai-Things seems to work. Things like res.body.savedResults.should.include.something.that.equals({title: 'Blah'}) error out I'm assuming since the record object contains other keys and values besides just title.
Is there a way to make it do what I want? I just need to verify that the titles are in the array and don't care what the other data might be (IE _id).
Thanks
This is what I usually do within the test:
var result = query_result;
var members = [];
result.forEach(function(e){
members.push(e.title);
});
expect(members).to.have.members(['expected_title_1','expected_title_2']);
If you know the order of the return array you could also do this:
expect(result).to.have.deep.property('[0].title', 'expected_title_1');
expect(result).to.have.deep.property('[1].title', 'expected_title_2');
As stated here following code works now with chai-like#0.2.14 and chai-things. I just love the natural readability of this approach.
var chai = require('chai'),
expect = chai.expect;
chai.use(require('chai-like'));
chai.use(require('chai-things')); // Don't swap these two
expect(data).to.be.an('array').that.contains.something.like({title: 'Blah'});
Probably the best way now a days would be to use deep.members property
This checks for unordered complete equality. (for incomplete equality change members for includes)
i.e.
expect([ {a:1} ]).to.have.deep.members([ {a:1} ]); // passes
expect([ {a:1} ]).to.have.members([ {a:1} ]); // fails
Here is a great article on testing arrays and objects
https://medium.com/building-ibotta/testing-arrays-and-objects-with-chai-js-4b372310fe6d
DISCLAIMER: this is to not only test the title property, but rather a whole array of objects
ES6+
Clean, functional and without dependencies, simply use a map to filter the key you want to check
something like:
const data = [{_id: 5, title: 'Blah', owner: 'Ted', description: 'something'},{_id: 70, title: 'GGG', owner: 'Ted', description: 'something'}];
expect(data.map(e=>({title:e.title}))).to.include({title:"Blah"});
or even shorter if you only check one key:
expect(data.map(e=>(e.title))).to.include("Blah");
https://www.chaijs.com/api/bdd/
Here is another approach that I found to be more helpful. Basically, use string interpolation and map your array of objects to an array of string literals. Then you can write expectations against the array of strings.
const locations: GeoPoint[] = [
{
latitude: 10,
longitude: 10
},
{
latitude: 9,
longitude: 9
},
{
latitude: -10,
longitude: -10
},
{
latitude: -9,
longitude: -9
}
];
const stringLocations: string[] = locations.map((val: GeoPoint) =>
`${val.latitude},${val.longitude}`
);
expect(stringLocations).to.contain('-9.5,-9.5');
expect(stringLocations).to.contain('9.5,9.5');
I loved the suggestion from #sebastien-horin
But another way with Should syntax (for the specific property):
const data = [
{ _id: 5, title: 'Blah', owner: 'Ted', description: 'something' },
{ _id: 7, title: 'Test', owner: 'Ted', description: 'something' },
];
data.map((e) => e.title).every((title) => title.should.equal('Blah'));
An alternative solution could be extending the array object with a function to test if an object exists inside the array with the desired property matching the expected value, like this
/**
* #return {boolean}
*/
Array.prototype.HasObjectWithPropertyValue = function (key, value) {
for (var i = 0; i < this.length; i++) {
if (this[i][key] === value) return true;
}
return false;
};
(i put this in my main test.js file, so that all other nested tests can use the function)
Then you can use it in your tests like this
var result = query_result;
// in my case (using superagent request) here goes
// var result = res.body;
result.HasObjectWithPropertyValue('property', someValue).should.equal(true);

Get elements inside javascript object that shares similar property names

I want to know if there is a way to extract / search the elements which contain similar property from a javascript object.
Just to be more specific, If I have the following object:
Live Demo: http://jsfiddle.net/oscarj24/sMWUL/
var enrolled = {};
enrolled['enrolled/ruby/S1234'] = {
course: {
id: 'P01',
desc: 'Ruby course'
},
student: {
id: 'S1234',
name: 'John Doe'
}
};
enrolled['enrolled/php/S1234'] = {
course: {
id: 'P02',
desc: 'PHP course'
},
student: {
id: 'S1234',
name: 'Foo Bar'
}
};
enrolled['enrolled/java/S6666'] = {
course: {
id: 'P03',
desc: 'Java course'
},
student: {
id: 'S6666',
name: 'Bill Gates'
}
};
Then I'll have some similar properties inside the enrolled object (like the ones sharing the S1234 string at the end).
So, my question is:
How can I extract the elements with string similarities or coincidences in the properties?
I've looked that javascript objects are very limited and the only thing I can do to check if a property exists or not is: obj.hasOwnProperty(prop) (but this is not what I am looking for). Is there a way to use regex to check for this? (just saying, seems not to be possible).
Just to know, I am using ExtJS 4.2 but I can't find something to achieve what I need in the API documentation (correct me if wrong).
You can use a for each loop to see if what you're searching for is in the string.`
for (key in enrolled)
{
if(key.indexOf('S1234') > -1)
{
console.log(key);
//Perform your actions here
}
}
Fiddle here: http://jsfiddle.net/brettwlutz/sMWUL/2/
how about this?
for(var prop in enrolled){
console.log(prop);
//check if prop matches string regex.
}

Aggregating array data in a computed observer, but maintaining state in KnockoutJS

This is a rather complex issue. I've tried to refine the code as much as possible to only that which is required to demonstrate the problem. This is a very long post - consider yourself warned!
A working demonstration of this issue can be seen at this jsFiddle.
What I'm trying to do is aggregate an array into groups based on a common property of the items in the array. Actually aggregating the arrays into groups is fairly trivial:
function ViewModel() {
var self = this;
this.groupProperty = ko.observable('groupId');
this.data = ko.observableArray();
this.view = ko.computed(function () {
var i, element, value, grouped = {};
for (i = 0; i < self.data().length; i++) {
element = self.data()[i];
if (!element.hasOwnProperty(self.groupProperty()))
continue; //Exclude objects that don't have the grouping property...
value = ko.isObservable(element[self.groupProperty()]) ? element[self.groupProperty()]() : element[self.groupProperty];
if (!grouped.hasOwnProperty(value))
grouped[value] = new Group(value);
grouped[value].items().push(element);
}
return transformObjectIntoArray(grouped);
});
}
transformObjectIntoArray can be seen in the jsFiddle example. It just turns an object into an array by getting rid of the properties basically.
This takes an array of data like:
[
Item { label: 'Item 1', groupId: 'A' },
Item { label: 'Item 2', groupId: 'A' },
Item { label: 'Item 1', groupId: 'B' },
Item { label: 'Item 2', groupId: 'B' }
]
Turns it into the following object:
{
'A': [
Item { label: 'Item 1', groupId: 'A' },
Item { label: 'Item 2', groupId: 'A' }
],
'B': [
Item { label: 'Item 1', groupId: 'B' },
Item { label: 'Item 2', groupId: 'B' }
]
}
Which is then transformed into the following array:
[
[
Item { label: 'Item 1', groupId: 'A' },
Item { label: 'Item 2', groupId: 'A' }
],
[
Item { label: 'Item 1', groupId: 'B' },
Item { label: 'Item 2', groupId: 'B' }
]
]
Everything works as intended up to this point, as can be seen in the jsFiddle.
The problem begins when I add a new element to the data array. vm is my ViewModel instance in the following code
vm.data.push(new Item('Item X', 'A')); //Add a new item called 'Item X' to the 'A' group
As you might have guessed, this causes the computed observable view to execute, which re-aggregates the underlying data array. All new Group objects are created, which means, any state information associated with them is lost.
This is a problem for me because I store the state of if they should be expanded or collapsed on the Group object:
function Group(label) {
this.expanded = ko.observable(false); //Expanded state is stored here
this.items = ko.observableArray();
this.label = ko.observable(label);
}
So, since they get recreated, that state information is lost. Thus, all groups revert to their default state (which is collapsed).
I know the problem I'm facing. However, I'm struggling to come up with a solution that isn't unwieldy. Possible solutions I've thought of include the following:
Maintaining a group state map
The first idea I had was to create an object that would serve as a map. It would use the groupId as the key name, and the state would be the value:
{
'A': GroupState { expanded: true },
'B': GroupState { expanded: false }
}
Even though the groups are getting recreated each time an element is added, the GroupState would persist. A problem I couldn't solve was how to remove groups that no longer exist from the GroupState map.
For example,
vm.data(someNewArray);
Where someNewArray was an array of items with no groupIds corresponding to those currently in the GroupState map. How would I remove the entries that no longer had a reference? This seems like it would be a memory leak in my application.
This problem can be seen demonstrated at this jsFiddle. Notice, after clicking the button, the group state size grows to 5 elements, but there are only 3 groups. This is because the original 2 groups aren't removed despite no longer being used.
Maintaining a viewState array and removing the computed observable
The second idea I had was to remove the computed observable view. Instead, I'd have a second observable array called viewState which would be the data array after it had been aggregated into the current "view". However, I quickly ran into problems with this idea.
First, I'd have to write a couple methods that would maintain state between the two arrays: add(), remove(), clear(), etc. Considering having to do this made me immediately start questioning if having a second array was a good idea at all.
Second, removing the computed array means linearly searching the viewState array to see if any current elements contain a groupId similar to the incoming item. While in practice, this would be blazing fast, I don't like the theory of iterating the entire array on each add (O(n) vs O(1)). Down the road, I might be working with thousands of items.
I feel like there is probably an easy solution to this, but I'm struggling to put my finger on it. Does anyone have any ideas that might help with this problem, or know an entirely better approach to accomplishing this with KnockoutJS? Perhaps, one of my ideas above will work with some added insight (I still feel like the GroupState map was on the right track)
Please let me know if I've left out some crucial information and I will do my best to add it
What about this proposition:
don't use any computed
during creation of a new item, create the group if it does not exist and add the item to the group
Note:
I am using arrayFirst to find if a group exist during the creation of an item, it is O(n) but you can instead store groups in properties so that the lookup should be O(log(n)) (not tested)
function Item(label, groupId) {
var self = this;
self.label = ko.observable(label);
self.groupId = ko.observable(groupId);
}
function Group(label) {
var self = this;
self.expanded = ko.observable(false);
self.items = ko.observableArray();
self.label = ko.observable(label);
self.addItem = function(item) {
self.items.push(item);
}
}
function ViewModel() {
var self = this;
self.groups = ko.observableArray();
self.addItem = function(label, groupId) {
var group = ko.utils.arrayFirst(self.groups(), function(gr) {
return groupId == gr.label();
});
if(!group) {
console.log('not group');
group = self.addGroup(groupId);
}
var item = new Item(label, groupId);
group.addItem(item);
}
self.addGroup = function(groupId) {
var group = new Group(groupId);
this.groups.push(group);
return group;
}
this.buttonPressed = function () {
vm.addItem("Item X", "A");
};
}
var vm = new ViewModel();
ko.applyBindings(vm);
vm.addItem("Item 1", 'A');
vm.addItem("Item 2", 'A');
vm.addItem("Item 1", 'B');
vm.addItem("Item 2", 'B');
JSFiddle link

Categories