I have an array of products
which in turn have an array of categories
. I want to extract distinct values of the type
property on category
object.
Both the Lodash and native versions below do the job.
I want to make a generic function which takes the path of property and return unique values.
Essentially I am looking at something terse like
map(products, property("categories[].type")
but heres the longer version(s)
import { compact, flatten, map, property, uniq } from "lodash";
export const getAllTypes1 = (products) => {
return uniq(
compact(map(flatten(map(products, property("categories"))), "type"))
);
};
export const getAllTypes2 = (products) => {
const types = [];
products.forEach((product) => {
product.categories.forEach((category) => {
if (!types.some((t) => t === category.type)) {
types.push(category.type);
}
});
});
return types;
};
Example data
const product1 = {
name: 'Wilson Orange',
price: 72.50,
categories: [{
type: 'flash sale',
discountable: false,
},{
type: 'tennis',
discountable: true,
}]
};
const product2 = {
name: 'Babolat Green',
price: 65.50,
categories: [{
type: 'tennis',
discountable: true,
}]
};
const products = [product1, product2];
Result
const result = getAllTypes2(products);
console.log(result); // ["flash sale", "tennis"]
Here's a working example
CodePudding user response:
Here's a vanilla JS function that takes the path without needing []
and automatically checks arrays wherever it finds one.
How it works is:
- Create an empty
Set
to easily remove duplicates - Turn the path string to an array of properties ->
props
- Call a recursive function
recurse(currObj, props)
which:- Checks if the
currObj
is an array, and if it is: a. Recurses again with the array values ascurrObj
b. Use the sameprops
since we didn't check an object in the path - Check if we're at the last prop in the path, if yes a. Add the property's value in the current object to the set
- Otherwise. recurse with
currObj[currProp]
, and the remaining props
- Checks if the
- Convert the set to an array and return it.
const product1 = {
name: 'Wilson Orange',
price: 72.5,
categories: [
{
type: 'flash sale',
discountable: false,
},
{
type: 'tennis',
discountable: true,
},
],
};
const product2 = {
name: 'Babolat Green',
price: 65.5,
categories: [
{
type: 'tennis',
discountable: true,
},
],
};
const products = [product1, product2];
function getProperties(array, path) {
const props = path.split('.');
const values = new Set();
function recurse(currObj, props) {
const currProp = props[0]
const nextProps = props.slice(1);
if (Array.isArray(currObj)) {
for (let val of currObj) {
recurse(val, props);
}
return
}
if (nextProps.length === 0) {
values.add(currObj[currProp])
} else {
recurse(currObj[currProp], nextProps)
}
}
recurse(array, props);
return [...values];
}
console.log(getProperties(products,'categories.type'))
console.log(getProperties(products,'price'))
console.log(getProperties(products,'name'))
CodePudding user response:
It's not a property path string, but it's pretty terse and expressive:
const pipe = (...fs) => fs.reduceRight(
(next, f) => x => f(x, next), x => x,
);
const getAllTypes = pipe(
(x, next) => [...new Set(x.flatMap(next))],
(x, next) => x.categories.map(next),
(x) => x.type,
);
// equivalent to
// const getAllTypes =
// x => [...new Set(x.flatMap(y => y.categories.map(z => z.type)))];
const products = [{
name: 'Wilson Orange',
price: 72.50,
categories: [{
type: 'flash sale',
discountable: false,
}, {
type: 'tennis',
discountable: true,
}]
}, {
name: 'Babolat Green',
price: 65.50,
categories: [{
type: 'tennis',
discountable: true,
}]
}];
console.log(getAllTypes(products));
The parameter x => x
allows pipe
to be called without any arguments and return the identity function. It also allows the last function argument of pipe
to accept a next
parameter for consistency, i.e. (x, next) => next(x.type)
would have been equivalent to (x) => x.type
.
Reference: