Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

fix(filterFilter): filter deep object by string #10401

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 38 additions & 23 deletions src/ng/filter/filter.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,6 @@ function filterFilter() {
if (!isArray(array)) return array;

var predicateFn;
var matchAgainstAnyProp;

switch (typeof expression) {
case 'function':
Expand All @@ -129,11 +128,8 @@ function filterFilter() {
case 'boolean':
case 'number':
case 'string':
matchAgainstAnyProp = true;
//jshint -W086
case 'object':
//jshint +W086
predicateFn = createPredicateFn(expression, comparator, matchAgainstAnyProp);
predicateFn = createPredicateFn(expression, comparator);
break;
default:
return array;
Expand All @@ -144,7 +140,7 @@ function filterFilter() {
}

// Helper functions for `filterFilter`
function createPredicateFn(expression, comparator, matchAgainstAnyProp) {
function createPredicateFn(expression, comparator) {
var predicateFn;

if (comparator === true) {
Expand All @@ -163,52 +159,58 @@ function createPredicateFn(expression, comparator, matchAgainstAnyProp) {
}

predicateFn = function(item) {
return deepCompare(item, expression, comparator, matchAgainstAnyProp);
return deepCompare(item, expression, comparator);
};

return predicateFn;
}

function deepCompare(actual, expected, comparator, matchAgainstAnyProp) {
function deepCompare(actual, expected, comparator) {
var actualType = typeof actual;
var expectedType = typeof expected;

if ((expectedType === 'string') && (expected.charAt(0) === '!')) {
return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp);
} else if (actualType === 'array') {
return !deepCompare(actual, expected.substring(1), comparator);
}

if (actualType === 'array') {
// In case `actual` is an array, consider it a match
// if ANY of it's items matches `expected`
return actual.some(function(item) {
return deepCompare(item, expected, comparator, matchAgainstAnyProp);
return deepCompare(item, expected, comparator);
});
}

switch (actualType) {
case 'object':
var key;
if (matchAgainstAnyProp) {
for (key in actual) {
if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator)) {
return true;
}
}
return false;
} else if (expectedType === 'object') {
if (expectedType === 'object') {
for (key in expected) {
var expectedVal = expected[key];
if (isFunction(expectedVal)) {
continue;
}

var keyIsDollar = key === '$';
var actualVal = keyIsDollar ? actual : actual[key];
if (!deepCompare(actualVal, expectedVal, comparator, keyIsDollar)) {
return false;
if (keyIsDollar) {
if (!matchAnyProp(actual, expectedVal, comparator)) {
return false;
}
} else {
var actualVal = actual[key];
if (!deepCompare(actualVal, expectedVal, comparator)) {
return false;
}
}
}
return true;
} else {
return comparator(actual, expected);
for (key in actual) {
if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator)) {
return true;
}
}
return false;
}
break;
case 'function':
Expand All @@ -217,3 +219,16 @@ function deepCompare(actual, expected, comparator, matchAgainstAnyProp) {
return comparator(actual, expected);
}
}

function matchAnyProp(actual, expected, comparator) {
if (typeof actual !== 'object') {
throw new Error('actual must be an object but was ' + typeof actual);
}

for (var key in actual) {
if (deepCompare(actual[key], expected, comparator)) {
return true;
}
}
return false;
}
13 changes: 10 additions & 3 deletions test/ng/filter/filterSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ describe('Filter: filter', function() {
expect(filter(items, "I don't exist").length).toBe(0);
});

it('should filter deep object by string', function() {
var items = [{person: {name: 'Annet', email: 'annet@example.com'}},
{person: {name: 'Billy', email: 'me@billy.com'}},
{person: {name: 'Joan', email: {home: 'me@joan.com', work: 'joan@example.net'}}}];
expect(filter(items, 'me@joan').length).toBe(1);
expect(filter(items, 'joan@example').length).toBe(1);
});

it('should not read $ properties', function() {
expect(''.charAt(0)).toBe(''); // assumption
Expand Down Expand Up @@ -136,14 +143,14 @@ describe('Filter: filter', function() {
});


it('should respect the depth level of a "$" property', function() {
it('should match the same level and deeper of a "$" property', function() {
var items = [{person: {name: 'Annet', email: 'annet@example.com'}},
{person: {name: 'Billy', email: 'me@billy.com'}},
{person: {name: 'Joan', email: {home: 'me@joan.com', work: 'joan@example.net'}}}];
var expr = {person: {$: 'net'}};

expect(filter(items, expr).length).toBe(1);
expect(filter(items, expr)).toEqual([items[0]]);
expect(filter(items, expr).length).toBe(2);
expect(filter(items, expr)).toEqual([items[0], items[2]]);
});


Expand Down