Filter array of objects by value - javascript

I want to filter an array of objects, by a specific value within the objects.
In the example i've provided I want to filter the array 'pets' by a value in the array 'characteristics'. For example, where I have called the function with the param 'loyal', i'd only expect the object for the dog value to be returned, as only the dog has that characteristic.
At the moment when I call the function both objects are returned even though only the object for dog has that value in its characteristics array.
const pets = [
{
name: 'dog',
characteristics: [
{
value: 'loyal'
},
{
value: 'big'
}
]
},
{
name: 'cat',
characteristics: [
{
value: 'fluffy'
},
{
value: 'small'
}
]
},
]
function filterPets(pets, characteristic) {
return pets.filter(function(pet) {
return pet.characteristics.filter(o => o.value.includes(characteristic));
})
}
console.log(filterPets(pets, 'loyal'));

That's because for the characteristics check you're using filter, which always returns an array (even if a blank one), and even a blank array is a truthy value, so the outer filter keeps every pet you check. For that inner check, you want some, not filter, so you get a flag for whether any entries matched:
function filterPets(pets, characteristic) {
return pets.filter(function(pet) {
return pet.characteristics.some(o => o.value.includes(characteristic));
// −−−−−−−−−−−−−−−−−−−−−−−−−−−−^^^^
});
}
const pets = [
{
name: 'dog',
characteristics: [
{
value: 'loyal'
},
{
value: 'big'
}
]
},
{
name: 'cat',
characteristics: [
{
value: 'fluffy'
},
{
value: 'small'
}
]
},
];
function filterPets(pets, characteristic) {
return pets.filter(function(pet) {
return pet.characteristics.some(o => o.value.includes(characteristic));
});
}
console.log(filterPets(pets, 'loyal'));
Just for what it's worth, I assume characteristics are unique (you can't have "loyal" twice), so you might prefer to keep those in a Set so you can check for them more easily than .some(o => o.includes(characteristic)). For instance:
const pets = [
{
name: "dog",
characteristics: new Set(["loyal", "big"]),
},
{
name: "cat",
characteristics: new Set(["fluffy", "small"]),
},
];
function filterPets(pets, characteristic) {
return pets.filter(function(pet) {
return pet.characteristics.has(characteristic);
});
}
Live Example:
const pets = [
{
name: "dog",
characteristics: new Set(["loyal", "big"]),
},
{
name: "cat",
characteristics: new Set(["fluffy", "small"]),
},
];
function filterPets(pets, characteristic) {
return pets.filter(function(pet) {
return pet.characteristics.has(characteristic);
});
}
console.log(filterPets(pets, "loyal"));
console.log("Don't worry about the {} for characteristics, the Stack Snippets console doesn't know how to display Set objects. Look in the real console if you want to double-check the set.");

function filterPets(list, charValue) {
const filteredPets = []
list.map(function(pet,petIndex,array) {
pet.characteristics.map(function(charac){
if(charac.value === charValue){
return filteredPets.push(array[petIndex])
}
})
})
return filteredPets
}
filterPets(pets,'loyal');

Related

Return all values of nested arrays using string identifier

Given an object searchable, is there a simple way of returning all the id values using lodash or underscore.js (or equivalent) where I can define the path to id?
const searchable = {
things: [
{
id: 'thing-id-one',
properties: [
{ id: 'd1-i1' },
{ id: 'd1-i2' },
]
},
{
id: 'thing-id-two',
properties: [
{ id: 'd2-i1' },
{ id: 'd2-i2' },
]
}
]
}
I am looking to see if this is possible in a manner similar to how we can use lodash.get e.g. if we wanted to return the things array from searchable we could do
const things = _.get(searchable, 'things');
I can't seem to find anything similar in the documentation. I am looking for something
that could contain an implementation similar to:
_.<some_function>(searchable, 'things[].properties[].id')
Note: I am well aware of functions like Array.map etc and there are numerous ways of extracting the id property - it is this specific use case that I am trying to figure out, what library could support passing a path as a string like above or does lodash/underscore support such a method.
Found a solution using the package jsonpath
const jp = require('jsonpath');
const result = jp.query(searchable, '$.things[*].properties[*].id')
console.log(result);
// outputs: [ 'd1-i1', 'd1-i2', 'd2-i1', 'd2-i2' ]
you can do it easily in plain js
like this
const searchable = {
things: [
{
id: 'thing-id-one',
properties: [
{ id: 'd1-i1' },
{ id: 'd1-i2' },
]
},
{
id: 'thing-id-two',
properties: [
{ id: 'd2-i1' },
{ id: 'd2-i2' },
]
}
]
}
const search = (data, k) => {
if(typeof data !== 'object'){
return []
}
return Object.entries(data).flatMap(([key, value]) => key === k ? [value]: search(value, k))
}
console.log(search(searchable, 'id'))
_.map and _.flatten together with iteratee shorthands let you expand nested properties. Every time you need to expand into an array, just chain another map and flatten:
const searchable = {
things: [
{
id: 'thing-id-one',
properties: [
{ id: 'd1-i1' },
{ id: 'd1-i2' },
]
},
{
id: 'thing-id-two',
properties: [
{ id: 'd2-i1' },
{ id: 'd2-i2' },
]
}
]
}
// Let's say the path is "things[].properties[].id"
const result = _.chain(searchable)
.get('things').map('properties').flatten()
.map('id').value();
console.log(result);
<script src="https://cdn.jsdelivr.net/npm/underscore#1.13.4/underscore-umd-min.js"></script>

Trying to return a filter() value from an array of objects based on conditionals

I'm trying to return only a part of my object as an array based on conditionals but I'm kinda stumped.
I have an array of objects and I want to return an array of names for each key : value they fit in.
I do get only the unique pairings but I'm returning the food key:value as well and all of it is still inside an object not a new array. Some insight would be greatly appreciated. Newer to coding.
const organizeNames = function (foods) {
let foodNames = foods.filter((names) => {
if (names.food === 'oranges') {
return names.name;
}
});
console.log(foodNames);
};
console.log(
organizeNames([
{ name: 'Samuel', food: 'oranges' },
{ name: 'Victoria', food: 'pizza' },
{ name: 'Karim', food: 'pizza' },
{ name: 'Donald', food: 'pizza' },
])
);
You're really close here. What you need to incorporate is .map() to map your list of objects to a list of names. Your filter part works, partly by accident, so I've fixed it to be more correct (return names.food === 'oranges'), and then once you have your list of objects that match 'oranges' for their food, you map that filtered list into a list of names by doing .map(names => names.name)
const organizeNames = function (foods) {
let foodNames = foods.filter((names) => {
// only keep items whose .food property === 'oranges'
return names.food === 'oranges'; // returns true or false
}).map(names => {
// map the filtered list of objects into a list of names by
// returning just the object's .name property
return names.name;
});
return foodNames;
};
console.log(
organizeNames([
{ name: 'Samuel', food: 'oranges' },
{ name: 'Victoria', food: 'pizza' },
{ name: 'Karim', food: 'pizza' },
{ name: 'Donald', food: 'pizza' },
])
);
The filter() callback function should just return true or false. Then you can use map() to get a specific property from each of the filtered items.
const organizeNames = function(foods) {
let foodNames = foods.filter((names) => names.food == 'oranges').map(names => names.name);
return foodNames;
}
console.log(
organizeNames([{
name: 'Samuel',
food: 'oranges'
},
{
name: 'Victoria',
food: 'pizza'
},
{
name: 'Karim',
food: 'pizza'
},
{
name: 'Donald',
food: 'pizza'
},
])
);

Search Inside Dropdown with data in tree structure React JS

I have developed a custom component which renders dropdown with a tree like structure inside it and allows the user to search for values inside the dropdown. Somehow the search works only after two levels of the tree structure.
We would be able to search only on the inside of NextJS label. The previous levels do not render results.
My function looks like this:
const searchFunction = (menu: treeData[], searchText: string) => {
debugger; //eslint-disable-line no-debugger
for (let i = 0; i < menu.length; i++) {
if (menu[i].name.includes(searchText)) {
setFound(true);
return menu[i].name;
} else if (!menu[i].name.includes(searchText)) {
if (menu[i].children !== undefined) {
return searchFunction(menu[i].children, searchText);
}
} else {
return 'Not Found';
}
}
};
And My data is like this:
import { treeData } from './DdrTreeDropdown.types';
export const menu: treeData[] = [
{
name: 'Web Project',
children: [
{
name: 'NextJS',
children: [
{
name: 'MongoDB',
},
{
name: 'Backend',
children: [
{
name: 'NodeJS',
},
],
},
],
},
{
name: 'ReactJS',
children: [
{
name: 'Express',
},
{
name: 'mysql',
children: [
{
name: 'jwt',
},
],
},
],
},
],
},
{
name: 'lorem project',
children: [
{
name: 'Vue Js',
children: [
{
name: 'Oracle Db',
},
{
name: 'JDBC',
children: [
{
name: 'Java',
},
],
},
],
},
{
name: 'ReactJS',
children: [
{
name: 'Express',
},
{
name: 'mysql',
children: [
{
name: 'jwt',
},
],
},
],
},
],
},
];
The sandbox link of the component is here:
https://codesandbox.io/s/upbeat-feynman-89ozi?file=/src/styles.ts
I haven't looked at the context that this is used in, so apologies if I'm missing something about how this is supposed to work. I've assumed that you can call setFound after running this function based on whether it finds anything or not and that it only needs to return one value. But hopefully this helps.
const menu = [{"name":"Web Project","children":[{"name":"NextJS","children":[{"name":"MongoDB"},{"name":"Backend","children":[{"name":"NodeJS"}]}]},{"name":"ReactJS","children":[{"name":"Express"},{"name":"mysql","children":[{"name":"jwt"}]}]}]},{"name":"lorem project","children":[{"name":"Vue Js","children":[{"name":"Oracle Db"},{"name":"JDBC","children":[{"name":"Java"}]}]},{"name":"ReactJS","children":[{"name":"Express"},{"name":"mysql","children":[{"name":"jwt"}]}]}]}];
const searchFunction = (menu, searchText) => {
let result;
for(let i = 0; i < menu.length; i++) {
if(menu[i].name.includes(searchText)) {
return menu[i].name;
} else if(menu[i].children !== undefined) {
result = searchFunction(menu[i].children, searchText);
if(result) return result;
}
}
return null;
};
console.log(searchFunction(menu, 'NextJS'));
console.log(searchFunction(menu, 'jwt'));
console.log(searchFunction(menu, 'foo'));
Looking at why the current version doesn't work, I think it goes something like this:
Let's take 'jwt' as the searchText.
We start in the 'Web Project' object, the name does not match, so we go to the else if block (BTW, we can never reach the else block as the else if condition is the opposite of the if condition).
The 'Web Project' object does have children so we will return from the new call to searchFunction; notice that 'lorem project' can never be reached as we will (regardless of the result) return the value of searchFunction and skip the rest of the loop.
Inside of our new and subsequent calls to searchFunction the same is going to happen until we find either a matching item or an item without children.
If we get to an item without children the the loop will successfully carry on to the siblings of the item.
If it doesn't find a match or an item with children it will exit the for loop and return undefined up the chain to the caller of the initial searchFunction.

Updated nested object by matching ID

I have an array with nested objects that I need to update from another array of objects, if they match.
Here is the data structure I want to update:
const invoices = {
BatchItemRequest: [
{
bId: "bid10",
Invoice: {
Line: [
{
SalesItemLineDetail: {
ItemAccountRef: { AccountCode: "10110" },
},
},
{
SalesItemLineDetail: {
ItemAccountRef: { AccountCode: "11110" },
},
Amount: 2499,
},
],
},
},
{
bId: "bid10",
Invoice: {
Line: [
{
SalesItemLineDetail: {
ItemAccountRef: { AccountCode: "10110" },
},
},
{
SalesItemLineDetail: {
ItemAccountRef: { AccountCode: "10111" },
},
Amount: 2499,
},
],
},
},
],
};
Here is the array of objects I want to update it from:
const accounts = [
{ AccountCode: "10110", Id: "84" },
{ AccountCode: "11110", Id: "5" },
{ AccountCode: "10111", Id: "81" },
];
I want to update invoices, using accounts, by inserting Id if AccountCode matches, to get the following structure:
const invoices = {
BatchItemRequest: [
{
bId: "bid10",
Invoice: {
Line: [
{
SalesItemLineDetail: {
ItemAccountRef: { AccountCode: "10110", Id: "84" },
},
},
{
SalesItemLineDetail: {
ItemAccountRef: { AccountCode: "11110", Id: "5" },
},
Amount: 2499,
},
],
},
},
{
bId: "bid10",
Invoice: {
Line: [
{
SalesItemLineDetail: {
ItemAccountRef: { AccountCode: "10110", Id: "84" },
},
},
{
SalesItemLineDetail: {
ItemAccountRef: { AccountCode: "10111", Id: "81" },
},
Amount: 2499,
},
],
},
},
],
};
I have tried various methods, such as the following:
const mapped = invoices.BatchItemRequest.map((item1) => {
return Object.assign(
item1,
accounts.find((item2) => {
return item2 && item1.Invoice.Line.ItemAccountRef.AccountCode === item2.AccountCode;
})
);
});
Problem with this approach (it doesn't work as I think I need to do another nested map), but it also creates a new array, only including the nested elements of invoices.
Does anyone know a good approach to this?
This isn't the cleanest of code but it gets the job done:
function matchInvoiceWithAccount(invoices, accounts) {
const mappedInvoices = invoices.BatchItemRequest.map((request) => {
// Shouldn't modify input parameter, could use Object.assign to create a copy and modify the copy instead for purity
request.Invoice.Line = request.Invoice.Line.map((line) => {
const accountCode = line.SalesItemLineDetail.ItemAccountRef.AccountCode;
// If accounts was a map of AccountCode to Id you would't need to search for it which would be more effective
const account = accounts.find((account) => account.AccountCode === accountCode);
if (account) {
line.SalesItemLineDetail.ItemAccountRef.Id = account.Id;
}
return line;
});
return request;
});
return {
BatchItemRequest: mappedInvoices,
};
}
What you could and probably should do to improve this is to not modify the input parameters of the function, but that requires that you in a better way copy the original, either using Object.assign or spread operator.
At first, it will be good to create Map from your accounts array. We will go one time for array with O(n) and then will read ids by code with O(1). And nested fors is O(m*n), that will be much more slower at big arrays.
const idsByAccountCodes = new Map();
accounts.forEach((data) => {
idsByAccountCodes.set(data.AccountCode, data.Id);
})
or shorter:
const idsByAccountCode = new Map(accounts.map((data) => [data.AccountCode, data.Id]))
then if you want to mutate original values you can go through all nesting levels and add values
for ( const {Invoice:{ Line: line }} of invoices.BatchItemRequest){
for ( const {SalesItemLineDetail: {ItemAccountRef: item}} of line){
item.Id = idsByAccountCodes.get(item.AccountCode) || 'some default value'
// also if you don't have ids for all codes you need to define logic for that case
}
}
If you don't need to mutate original big object "invoices" and all of nested objects, then you can create recursive clone of if with something like lodash.cloneDeep

Combine several objects with weird structure into one

I have an array with a few objects which I'm trying to combine into one single object
[
{ user: { '563D': { pId: '12', seasion: '120', value: true } } },
{ user: { '563D': { pId: '11', seasion: '120', value: false } } },
...
]
pId is unique
seasion is the same for each object (almost never changing)
value can be anything
I want to have something like this:
{
id: '563D',
seasion: '120',
types: {
12: // pId
{
value: true
},
11:
{
value: false
}
}
}
I tried to use reduce and forEach but I wasnt been able to achieve my goals
due to poor understanding of those 2 methods.
EDIT:
forgot about several users input, sorry
[
{
users: {
'563D': [Object],
'4b07': [Object]
}
},
{
users: {
'563D': [Object],
'4b07': [Object]
}
},
{
users: {
'563D': [Object],
'4b07': [Object]
}
}
]
You could use reduce and destructuring to group the objects based on the first key inside user. Then use Object.values() to get the array of each group values:
Get user from each object by destructuring the parameter
Destructure the first entry of the user to get the key (like '563D') and the nested properties separately)
Use the || operator to check if the accumulator already has the id as it's property. If yes, use it. Else assign a new value with { id, seasion, types: {} }. This is using the Shorthand property names.
Update the types with pId as key and { value } as it's value
const input = [{user:{'563D':{pId:'12',seasion:120,value:true}, '4b07':{pId:'12',seasion:120,value:true}}},{user:{'563D':{pId:'11',seasion:120,value:false},'4b07':{pId:'11',seasion:120,value:false}}}]
const output = input.reduce((r, { user }) => {
for(const [id, { pId, seasion, value }] of Object.entries(user)) {
r[id] = r[id] || { id, seasion, types: {} };
r[id].types[pId] = { value };
}
return r;
}, {})
console.log(Object.values(output))
If you have only one unique id in the array, you can simplify the reduce to:
const input = [{user:{'563D':{pId:'12',seasion:120,value:true}}},{user:{'563D':{pId:'11',seasion:120,value:false}}}]
const output = input.reduce((r, { user }) => {
const [id, { pId, seasion, value }] = Object.entries(user)[0];
return { id, seasion, types: { ...r.types, [pId]: { value } } }
}, {})
console.log(output)

Categories

Resources