1

Consider the following example:

var products = {
    "Products": [{
        "Title": "A",
        "Categories": [{
            "Name": "Type",
            "Properties": ["Type 1", "Type 2", "Type 3"]
        }, {
            "Name": "Market",
            "Properties": ["Market 1", "Market 2", "Market 3", "Market 4"]
        }, {
            "Name": "Technology",
            "Properties": ["Tech 1", "Tech 2"]
        }]
    }, {
        "Title": "B",
        "Categories": [{
            "Name": "Type",
            "Properties": ["Type 1", "Type 3"]
        }, {
            "Name": "Market",
            "Properties": "Market 1"
        }, {
            "Name": "Technology",
            "Properties": ["Tech 1", "Tech 3"]
        }]
    }, {
        "Title": "C",
        "Categories": [{
            "Name": "Type",
            "Properties": ["Type 1", "Type 2", "Type 3"]
        }, {
            "Name": "Market",
            "Properties": ["Market 2", "Market 3"]
        }, {
            "Name": "Technology",
            "Properties": ["Tech 2", "Tech 3"]
        }]
    }]
}

I'm trying to filter products by their properties so consider I'm using an array to keep track of my selected filters:

var filters = ['Type 3', 'Tech 1'];

With these filters I would like to return product A and product B.

I currently have this:

var flattenedArray = _.chain(products).map('Categories').flatten().value();
var result= _.some(flattenedArray , ['Properties', 'Tech 1']);

But I'm stuck on how to combine the properties for a combined search.

2
  • Your filters always relate to Properties? Commented Dec 14, 2016 at 10:31
  • Yup, it always does. Commented Dec 14, 2016 at 10:32

4 Answers 4

2

Use _.filter() to iterate the products. For each product combine the list of properties using _.flatMap(), and use _.intersection() and _.size() to find the amount of filters that exist in the categories. Compare that to the original number of filters, and return comparison's response.

var products = {"Products":[{"Title":"A","Categories":[{"Name":"Type","Properties":["Type 1","Type 2","Type 3"]},{"Name":"Market","Properties":["Market 1","Market 2","Market 3","Market 4"]},{"Name":"Technology","Properties":["Tech 1","Tech 2"]}]},{"Title":"B","Categories":[{"Name":"Type","Properties":["Type 1","Type 3"]},{"Name":"Market","Properties":"Market 1"},{"Name":"Technology","Properties":["Tech 1","Tech 3"]}]},{"Title":"C","Categories":[{"Name":"Type","Properties":["Type 1","Type 2","Type 3"]},{"Name":"Market","Properties":["Market 2","Market 3"]},{"Name":"Technology","Properties":["Tech 2","Tech 3"]}]}]};

var filters = ['Type 3', 'Tech 1'];

var result = _.filter(products.Products, function(product) {
  return filters.length === _(product.Categories)
      .flatMap('Properties')
      .intersection(filters)
      .size();
});

console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.2/lodash.min.js"></script>

Sign up to request clarification or add additional context in comments.

1 Comment

All properties in filters have to be on the product. So, 'Type 3 appears in C' but 'Tech 1' doesn't. In my example product A + B should be returned beceause they both have 'Type 3' AND 'Tech 1'.
0

If I understand you question correctly, this code may help:

_.filter(
    products.Products,
    product => _.difference(
        filters,
        _.chain(product.Categories).map(category => category.Properties).flatten().value()
    ).length === 0
);

It calculates a union of all properties for each product:

_.chain(product.Categories).map(category => category.Properties).flatten().value()

And then checks that it contains all filters array elements, using _.difference method.

Hope it helps.

Comments

0

another fancy way through _.conforms

var res = _.filter(
    products.Products, 
    _.conforms({'Categories': function(categories) {
        return _.chain(categories)
            .flatMap('Properties') // flat arrays 
            .uniq() // remove dublicates
            .keyBy() // transform to objects with Properties keys 
            .at(filters) // get objects values by filters
            .compact() // remove undefineds
            .size() // get size
            .eq(filters.length) // compare to filters size
            .value();
    }
}))

Comments

0

This will work for a list of items where the givenProperty you want to filter on is either a string like 'doorColour' or an array of strings representing the path to the givenProperty like ['town', 'street', 'doorColour'] for a value nested on an item as town.street.doorColour.

It also can filter on more than one value so you could you just need pass in an array of substrings representing the string values you want to keep and it will retain items that have a string value which contains any substring in the substrings array.

The final parameter 'includes' ensures you retain these values if you set it to false it will exclude these values and retain the ones that do not have any of the values you specified in the substrings array

import { flatMap, path } from 'lodash/fp';

const filteredListForItemsIncludingSubstringsOnAGivenProperty = (items, givenProperty, substrings, including=true) => flatMap((item) =>
substrings.find((substring) => path(givenProperty)(item) && path(givenProperty)(item).includes(substring))
  ? including
    ? [item]
    : []
  : including
  ? []
  : [item])(items);

E.g. fLFIISOAGP(contacts, ['person','name'], ['Joh','Pau',Pet']); with items of structure {contact, business:null, personal:{name:'John'}}.

For the original question - this will also work - I would use this repeatedly on a list of items to filter with different keys to filter on more than one property.

const firstFilteredResult = filteredListForItemsIncludingSubstringsOnAGivenProperty( products.Products, ["Categories", "0", "Properties"], ["Type 3"]);

const secondFilteredResult = filteredListForItemsIncludingSubstringsOnAGivenProperty( firstFilteredResult, ["Categories", "2", "Properties"], ["Tech 1"]);

expect(secondFilteredResult[0]['Title']).to.equal( "A"); expect(secondFilteredResult[1]['Title']).to.equal( "B"); expect(secondFilteredResult.length).to.equal(2);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.