Skip to content

Commit

Permalink
Merge pull request #36 from marmelab/support-deep-path
Browse files Browse the repository at this point in the history
Add Support For Deep Paths in Filters
  • Loading branch information
fzaninotto committed Mar 4, 2021
2 parents 1483b82 + 108d150 commit b4641b4
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 46 deletions.
7 changes: 3 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"webpack-cli": "^4.5.0"
},
"dependencies": {
"babel-runtime": "^6.26.0"
"babel-runtime": "^6.26.0",
"lodash": "^4.17.21"
},
"browserslist": "> 0.25%, not dead"
}
124 changes: 83 additions & 41 deletions src/Collection.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,78 @@
import get from 'lodash/get';

const every = (array, predicate) =>
array.reduce((acc, value) => acc && predicate(value), true);

const some = (array, predicate) =>
array.reduce((acc, value) => acc || predicate(value), false);

const getArrayOfObjectsPaths = (keyParts, item) =>
keyParts.reduce((acc, key, index) => {
// If we already found an array, we don't need to explore further
// For example with path `tags.name` when tags is an array of objects
if (acc != undefined) {
return acc;
}

let keyToArray = keyParts.slice(0, index + 1).join('.');
let keyToItem = keyParts.slice(index + 1).join('.')
let itemValue = get(item, keyToArray);

// If the array is at the end of the key path, we will process it like we do normally with arrays
// For example with path `deep.tags` where tags is the array. In this case, we return undefined
return Array.isArray(itemValue) && index < keyParts.length - 1
? [keyToArray, keyToItem]
: undefined;
}, undefined);

const getSimpleFilter = (key, value) => {
if (key.indexOf('_lte') !== -1) {
// less than or equal
let realKey = key.replace(/(_lte)$/, '');
return item => get(item, realKey) <= value;
}
if (key.indexOf('_gte') !== -1) {
// less than or equal
let realKey = key.replace(/(_gte)$/, '');
return item => get(item, realKey) >= value;
}
if (key.indexOf('_lt') !== -1) {
// less than or equal
let realKey = key.replace(/(_lt)$/, '');
return item => get(item, realKey) < value;
}
if (key.indexOf('_gt') !== -1) {
// less than or equal
let realKey = key.replace(/(_gt)$/, '');
return item => get(item, realKey) > value;
}
if (Array.isArray(value)) {
return item => {
if (Array.isArray(get(item, key))) {
// array filter and array item value: where all items in values
return every(
value,
v => some(get(item, key), itemValue => itemValue == v)
);
}
// where item in values
return value.filter(v => v == get(item, key)).length > 0;
}
}
return item => {
if (Array.isArray(get(item, key)) && typeof value == 'string') {
// simple filter but array item value: where value in item
return get(item, key).indexOf(value) !== -1;
}
if (typeof get(item, key) == 'boolean' && typeof value == 'string') {
// simple filter but boolean item value: boolean where
return get(item, key) == (value === 'true' ? true : false);
}
// simple filter
return get(item, key) == value;
};
}

function filterItems(items, filter) {
if (typeof filter === 'function') {
return items.filter(filter);
Expand All @@ -21,52 +90,25 @@ function filterItems(items, filter) {
return false;
};
}

let keyParts = key.split('.');
let value = filter[key];
if (key.indexOf('_lte') !== -1) {
// less than or equal
let realKey = key.replace(/(_lte)$/, '');
return item => item[realKey] <= value;
}
if (key.indexOf('_gte') !== -1) {
// less than or equal
let realKey = key.replace(/(_gte)$/, '');
return item => item[realKey] >= value;
}
if (key.indexOf('_lt') !== -1) {
// less than or equal
let realKey = key.replace(/(_lt)$/, '');
return item => item[realKey] < value;
}
if (key.indexOf('_gt') !== -1) {
// less than or equal
let realKey = key.replace(/(_gt)$/, '');
return item => item[realKey] > value;
}
if (Array.isArray(value)) {
if (keyParts.length > 1) {
return item => {
if (Array.isArray(item[key])) {
// array filter and array item value: where all items in values
return every(
value,
v => some(item[key], itemValue => itemValue == v)
);
let arrayOfObjectsPaths = getArrayOfObjectsPaths(keyParts, item);

if (arrayOfObjectsPaths) {
let [arrayPath, valuePath] = arrayOfObjectsPaths;
// Check wether any item in the array matches the filter
const filteredArrayItems = filterItems(get(item, arrayPath), {[valuePath]: value});
return filteredArrayItems.length > 0;
} else {
return getSimpleFilter(key, value)(item);
}
// where item in values
return value.filter(v => v == item[key]).length > 0;
}
}
return item => {
if (Array.isArray(item[key]) && typeof value == 'string') {
// simple filter but array item value: where value in item
return item[key].indexOf(value) !== -1;
}
if (typeof item[key] == 'boolean' && typeof value == 'string') {
// simple filter but boolean item value: boolean where
return item[key] == (value === 'true' ? true : false);
}
// simple filter
return item[key] == value;
};

return getSimpleFilter(key, value);
});
// only the items matching all filters functions are in (AND logic)
return items.filter(item =>
Expand Down
85 changes: 85 additions & 0 deletions src/Collection.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,16 @@ describe("Collection", () => {
expect(collection.getAll({ filter: { name: "b" } })).toEqual(expected);
});

it("should filter values with deep paths", () => {
const collection = new Collection([
{ name: "c", deep: { value: "c" } },
{ name: "a", deep: { value: "a" } },
{ name: "b", deep: { value: "b" } },
]);
const expected = [{ name: "b", deep: { value: "b" }, id: 2 }];
expect(collection.getAll({ filter: { "deep.value": "b" } })).toEqual(expected);
});

it("should filter boolean values properly", () => {
const collection = new Collection([
{ name: "a", is: true },
Expand Down Expand Up @@ -217,6 +227,81 @@ describe("Collection", () => {
expect(collection.getAll({ filter: { tags: "f" } })).toEqual([]);
});

it("should filter array values properly within deep paths", () => {
const collection = new Collection([
{ deep: { tags: ["a", "b", "c"] } },
{ deep: { tags: ["b", "c", "d"] } },
{ deep: { tags: ["c", "d", "e"] } },
]);
const expected = [
{ id: 0, deep: { tags: ["a", "b", "c"] } },
{ id: 1, deep: { tags: ["b", "c", "d"] } },
];
expect(collection.getAll({ filter: { 'deep.tags': "b" } })).toEqual(expected);
expect(collection.getAll({ filter: { 'deep.tags': "f" } })).toEqual([]);
});

it("should filter array values properly inside deep paths", () => {
const collection = new Collection([
{ tags: { deep: ["a", "b", "c"] } },
{ tags: { deep: ["b", "c", "d"] } },
{ tags: { deep: ["c", "d", "e"] } },
]);
const expected = [
{ id: 0, tags: { deep: ["a", "b", "c"] } },
{ id: 1, tags: { deep: ["b", "c", "d"] } },
];
expect(collection.getAll({ filter: { 'tags.deep': "b" } })).toEqual(expected);
expect(collection.getAll({ filter: { 'tags.deep': "f" } })).toEqual([]);
});

it("should filter array values properly with deep paths", () => {
const collection = new Collection([
{ tags: [{ name: "a" }, { name: "b" }, { name: "c" }] },
{ tags: [{ name: "b" }, { name: "c" }, { name: "d" }] },
{ tags: [{ name: "c" }, { name: "d" }, { name: "e" }] },
]);
const expected = [
{ id: 0, tags: [{ name: "a" }, { name: "b" }, { name: "c" }] },
{ id: 1, tags: [{ name: "b" }, { name: "c" }, { name: "d" }] },
];
expect(collection.getAll({ filter: { 'tags.name': "b" } })).toEqual(expected);
expect(collection.getAll({ filter: { 'tags.name': "f" } })).toEqual([]);
});

it("should filter array values properly when receiving several values within deep paths", () => {
const collection = new Collection([
{ deep: { tags: ["a", "b", "c"] } },
{ deep: { tags: ["b", "c", "d"] } },
{ deep: { tags: ["c", "d", "e"] } },
]);
const expected = [{ id: 1, deep: { tags: ["b", "c", "d"] } }];
expect(collection.getAll({ filter: { 'deep.tags': ["b", "d"] } })).toEqual(
expected
);
expect(
collection.getAll({ filter: { 'deep.tags': ["a", "b", "e"] } })
).toEqual([]);
});

it("should filter array values properly when receiving several values with deep paths", () => {
const collection = new Collection([
{ tags: [{ name: "a" }, { name: "b" }, { name: "c" }] },
{ tags: [{ name: "c" }, { name: "d" }, { name: "e" }] },
{ tags: [{ name: "e" }, { name: "f" }, { name: "g" }] },
]);
const expected = [
{ id: 0, tags: [{ name: "a" }, { name: "b" }, { name: "c" }] },
{ id: 1, tags: [{ name: "c" }, { name: "d" }, { name: "e" }] }
];
expect(collection.getAll({ filter: { 'tags.name': ["c"] } })).toEqual(
expected
);
expect(
collection.getAll({ filter: { 'tags.name': ["h", "i"] } })
).toEqual([]);
});

it("should filter array values properly when receiving several values", () => {
const collection = new Collection([
{ tags: ["a", "b", "c"] },
Expand Down

0 comments on commit b4641b4

Please sign in to comment.