Skip to content

Instantly share code, notes, and snippets.

@jherax
Last active July 18, 2024 15:02
Show Gist options
  • Save jherax/f11d669ba286f21b7a2dcff69621eb72 to your computer and use it in GitHub Desktop.
Save jherax/f11d669ba286f21b7a2dcff69621eb72 to your computer and use it in GitHub Desktop.
Filters an array of objects with multiple match-criteria.
type FilterOperator = 'AND' | 'OR';
type FiltersBy<T> = {
[K in keyof T]?: (value: T[K]) => boolean;
};
/**
* Factory function that creates a specialized function to filter
* arrays, by validating all filters (AND operator),
* or validating just one of the filters (OR operator).
* @param operator Method to validate all filters: AND, OR
* @returns Specialized function
*/
function arrayFilterFactory(operator: FilterOperator) {
const method = operator === 'AND' ? 'every' : 'some';
/**
* Filters an array of objects using custom predicates.
* @param array List of elements to filter
* @param filters Object with predicates as filters
* @returns Filtered array
*/
return function filterArrayBy<T>(array: T[], filters: FiltersBy<T>): T[] {
const filterKeys = Object.keys(filters);
return array.filter(item => {
// validates filters (AND|OR operator)
return filterKeys[method](prop => {
return filters[prop](item[prop]);
});
});
};
}
describe('Testing arrayFilterFactory()', () => {
const arrayFilterEvery = arrayFilterFactory('AND');
const arrayFilterSome = arrayFilterFactory('OR');
type Person = {
age: number;
name: string;
email: string;
nation: string;
};
const users: Person[] = [
{age: 28, name: 'John', email: 'johnson@mail.com', nation: 'USA'},
{age: 38, name: 'Marlin', email: 'marlin@mail.com', nation: 'England'},
{age: 35, name: 'Tom', email: 'tom@mail.com', nation: 'England'},
{age: 28, name: 'Mark', email: 'mark@mail.com', nation: 'England'},
];
it('should filter an array of objects validating just one of the filters', () => {
// filters are applied using OR operator
const filters: FiltersBy<Person> = {
nation: country => country.toUpperCase() === 'USA',
age: age => age < 30,
};
const expected: Person[] = [
{age: 28, name: 'John', email: 'johnson@mail.com', nation: 'USA'},
{age: 28, name: 'Mark', email: 'mark@mail.com', nation: 'England'},
];
const filtered = arrayFilterSome(users, filters);
expect(filtered).toStrictEqual(expected);
});
it('should filter an array of objects validating all filters', () => {
// filters are applied using OR operator
const filters: FiltersBy<Person> = {
age: age => age < 30,
nation: country => country.toUpperCase() === 'USA',
};
const expected: Person[] = [
{age: 28, name: 'John', email: 'johnson@mail.com', nation: 'USA'},
];
const filtered = arrayFilterEvery(users, filters);
expect(filtered).toStrictEqual(expected);
});
});
/**
* Filters an array of objects using custom predicates.
* @param array List of elements to filter
* @param filters Object with predicates as filters
* @returns Filtered array
*/
function filterArrayBy<T>(array: T[], filters: FiltersBy<T>): T[] {
const filterKeys = Object.keys(filters);
return array.filter(item => {
// validates all filters (AND operator)
return filterKeys.every(prop => {
return filters[prop](item[prop]);
});
});
}
type FiltersBy<T> = {
[K in keyof T]?: (value: T[K]) => boolean;
};
type Product = {
name: string;
color: string;
size: number;
locations: string[];
details: {length: number; width: number};
};
describe('Testing filterArrayBy()', () => {
it('should filter an array of products by multiple properties', () => {
const products: Product[] = [
{ name: 'A', color: 'Blue', size: 50, locations: ['USA', 'Europe'], details: { length: 20, width: 70 } },
{ name: 'B', color: 'Blue', size: 60, locations: [], details: { length: 20, width: 70 } },
{ name: 'C', color: 'Black', size: 70, locations: ['Japan'], details: { length: 20, width: 71 } },
{ name: 'D', color: 'Green', size: 50, locations: ['USA'], details: { length: 20, width: 71 } },
];
// filters are applied using AND operator
const filters: FiltersBy<Product> = {
size: size => size === 50 || size === 70,
color: color => ['blue', 'black'].includes(color.toLowerCase()),
locations: locations => !!locations.find(c => ['japan', 'usa'].includes(c.toLowerCase())),
details: details => details.length < 30 && details.width >= 70,
};
const expected: Product[] = [
{ name: 'A', color: 'Blue', size: 50, locations: ['USA', 'Europe'], details: { length: 20, width: 70 } },
{ name: 'C', color: 'Black', size: 70, locations: ['Japan'], details: { length: 20, width: 71 } },
];
const filtered = filterArrayBy(products, filters);
expect(filtered).toStrictEqual(expected);
});
});
const value = <T>(v: T) => (typeof v === 'string' ? v.toUpperCase() : v);
/**
* Filters an array of objects using a list of valid values.
* @param array List of elements to filter
* @param filters Object whith lists of valid values
* @returns Filtered array
*/
function filterArrayByEq<T extends {[key: string]: unknown}>(
array: T[],
filters: FiltersByEq<T>,
): T[] {
const filterKeys = Object.keys(filters);
return array.filter(item => {
// validates all filters (AND operator)
return filterKeys.every(key => {
// ignores empty filters
if (!filters[key].length) return true;
return filters[key].find(f => value(f) === value(item[key]));
});
});
}
type FiltersByEq<T> = {
[K in keyof T]?: T[K][];
};
type Product = {
name: string;
color: string;
size: number;
};
describe('Testing filterArrayByEq()', () => {
it('should filter an array of objects using a list of valid values', () => {
const products: Product[] = [
{name: 'A', color: 'Blue', size: 50},
{name: 'B', color: 'Blue', size: 60},
{name: 'C', color: 'Black', size: 70},
{name: 'D', color: 'Green', size: 50},
];
// Filters are applied using AND operator.
// Each filter uses a list of valid values, compared using === operator.
// String values are not case-sensitive.
const filters: FiltersByEq<Product> = {
color: ['BLUE', 'black'],
size: [70, 50],
};
const expected: Product[] = [
{name: 'A', color: 'Blue', size: 50},
{name: 'C', color: 'Black', size: 70},
];
const filtered = filterArrayByEq(products, filters);
expect(filtered).toStrictEqual(expected);
});
});
@elharmouchiA
Copy link

elharmouchiA commented Sep 4, 2021

Is it too hard to ask to give us a working example?

return filters[key](filter => filter === item[key]); }); }); } is not even valid syntax.

thank you pro
I'm using TypeScript
here the syntax of function

filterPlainArray

function filterPlainArray(array, filters) {
  const filterKeys = Object.keys(filters);
  return array.filter(item => {
    return filterKeys.some(key => {
      if (!filters[key].length) return true;
      return filters[key].find(filter => filter === item[key]);
    });
  });
}

Filters data is:

const filters = {typeTraitement: [0], typeExecution: [0]};

and dataArray

const dataArray = [
  {typeTraitement: 0, typeExecution: 1},
  {typeTraitement: 1, typeExecution: 0},
  {typeTraitement: 2, typeExecution: 0},
  {typeTraitement: 2, typeExecution: 3},
];

-result is empty-

const result = this.filterPlainArray(dataArray, filters);

result:

expected result:

[
  {typeTraitement: 0, typeExecution: 1},
  {typeTraitement: 1, typeExecution: 0},
  {typeTraitement: 2, typeExecution: 0},
];

remark:

the function works fine with any number in filters other than 0

@rcdegoma
Copy link

rcdegoma commented Sep 27, 2021

Question: What if the Value are multiple that is delimited by semicolon ";",

Sample Data :
Title multi value = "Station 1;Station 2"
Filter State = Station 1 //This is the user selected in a multi checkbox option

  const filterKeys = Object.keys(filters);
  return arr.filter(eachObj => {
    return filterKeys.every(eachKey => {
      if (!filters[eachKey].length) {
        return true; // passing an empty filter means that filter is ignored.
      }
      **return filters[eachKey].includes(eachObj[eachKey]);** 
      // The current behaviour, it only retrieves the exact data sample is "Station 1"= "Station 1"
    });
  });
};

@mohdatique
Copy link

mohdatique commented Mar 18, 2022

@jherax -Great, thanks for this brainstorming thread.
I am struggling with nested keys in json to compare dynamically.

my data

{
  id: 1,
  user_status: 'ACTIVE',
  roles: [
    {id: 'APP_ADMIN', name: 'App Admin'},
    {id: 'APP_USER', name: 'App User'},
  ],
  permissions: {apps: ['abc', 'pqr']},
}

I am currently filtering it by user_status because it is straightforward, but need your help to make dynamic condition for roles and apps which is inside permission. How i can create condition for apps and roles.

@refundr
Copy link

refundr commented Mar 18, 2022

@jherax - I can't ever shake your hand - but please accept this thank you so much for sharing this !
Cheers to writing better code with fewer headaches !

@MohdKamranAlam
Copy link

Hi foks, I am creating 2 level depth filtering(multi select check box) and need to filter out
image

  • could you please suggest me the code.

@MohdKamranAlam
Copy link

MohdKamranAlam commented May 12, 2022

  • This is my filter file:
    image

Hi foks, I am creating 2 level depth filtering(multi select check box) and need to filter out image

  • could you please suggest me the code.

@rakathal
Copy link

rakathal commented Aug 17, 2022

Consider I have the below data:

[
{
    apId: "c3:89:6f:1a:83:5e",
    description: "Kiruna Ramp Beacon",
    estimated: false,
    externalId: null,
    id: 718311,
    name: "BLE_1",
    posX: 6622.404970932526,
    posY: 2834.686893101607,
    posZ: 3012.490824884788,
    timestamp: 0,
    type: "BLE_BEACON"
},
{
    apId: "e4:97:29:25:41:73",
    description: "Kiruna Ramp Beacon",
    estimated: false,
    externalId: null,
    id: 718312,
    name: "BLE_2",
    posX: 6401.213050968167,
    posY: 2791.9930699182455,
    posZ: 2983.5542385630656,
    timestamp: 0,
    type: "BLE_BEACON"
},
{
    apId: "ce:76:58:8c:66:e1",
    description: "Kiruna Ramp Beacon",
    estimated: false,
    externalId: null,
    id: 718313,
    name: "BLE_3",
    posX: 6175.356128422606,
    posY: 2863.5582614152468,
    posZ: 2956.701987555463,
    timestamp: 0,
    type: "BLE_BEACON"
}
]

Expected output is:

[
{
    apId: "c3:89:6f:1a:83:5e",
    description: "Kiruna Ramp Beacon",
    name: "BLE_1",
    posX: 6622.404970932526,
    posY: 2834.686893101607,
    posZ: 3012.490824884788,
    type: "BLE_BEACON",
},
{
    apId: "e4:97:29:25:41:73",
    description: "Kiruna Ramp Beacon",
    name: "BLE_2",
    posX: 6401.213050968167,
    posY: 2791.9930699182455,
    posZ: 2983.5542385630656,
    type: "BLE_BEACON",
},
{
    apId: "ce:76:58:8c:66:e1",
    description: "Kiruna Ramp Beacon",
    name: "BLE_3",
    posX: 6175.356128422606,
    posY: 2863.5582614152468,
    posZ: 2956.701987555463,
    type: "BLE_BEACON"
}
]

How to do it in TypeScript?

@klayus
Copy link

klayus commented Sep 29, 2022

hey man! thank you a million!!! if you ever come to romania i'll buy you 10 beers!!!!!!!!!!!!!!!!!!!

@elpahlevi
Copy link

Hey dude, thanks for sharing! Your snippet is handy so I can solve my problem related to filtering data using multiple criteria

@MLS535
Copy link

MLS535 commented Jan 16, 2023

Great code!
@estherjsuh where you able to solve it? I have the same problem....

@circa10a
Copy link

I made a small lib to solve this problem
https://github.com/circa10a/filter-object-array

@AshrafulIslam2
Copy link

AshrafulIslam2 commented Feb 7, 2023

I have an array of Sizes which is Size = ["40","XL","43","M"]
This is what my Products JSON File looks like :

[{
    "productCatgoris": "womensfashion",
    "productSubCatgoris": "Kamiz",
    "productStoreName": "S-trade Store",
    "productId": "27",
    "productName": "Cotton Unstitched Salwar Kameez for Women - Pink",
    "productImage": "https://images.pexels.com/photos/15388429/pexels-photo-15388429.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1",
    "productPrice": "699",
    "productDiscount": "10",
    "offerStatus": false,
    "productBrand": "Strade",
    "ExpressDelivary": true,
    "FreeDelivary": true,
    "productStock": "60",
    "productSold": "10",
    "offerStartAndEnDate": "12-12-2021-01-04-2022",
    "offerName": "",
    "productDescription": ["Cotton Unstitched Salwar Kameez for Women - Pink"],
    "productSpecipication": {
      "Brand": "S-trade",
      "size": ["XL", "M", "L", "S", "2XL"],
      "gender": "Womens",
      "Color": ["Pink"]
    },
    "productShipping": "Dhaka city !!!! Feel free to buy from this site as we promise you same day delivery of any purchased items. Sookh helps you to purchase any goods or service with a very few steps. If you ever find any obligation of returning any purchased item or items, please review our Return & Refund Policy.",
    "productReviews": []
  },
  {
    "productCatgoris": "womensfashion",
    "productSubCatgoris": "Kamiz",
    "productStoreName": "S-trade Store",
    "productId": "28",
    "productName": "Cotton Unstitched Salwar Kameez for Women - Pink",
    "productImage": "https://images.pexels.com/photos/15388429/pexels-photo-15388429.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1",
    "productPrice": "699",
    "productDiscount": "10",
    "offerStatus": false,
    "productBrand": "Strade",
    "ExpressDelivary": false,
    "FreeDelivary": false,
    "productStock": "60",
    "productSold": "10",
    "offerStartAndEnDate": "12-12-2021-01-04-2022",
    "offerName": "",
    "productDescription": ["Cotton Unstitched Salwar Kameez for Women - Pink"],
    "productSpecipication": {
      "Brand": "S-trade",
      "size": ["41", "42", "43", "45"],
      "gender": "Womens",
      "Color": ["Pink"]
    },
    "productShipping": "Dhaka city !!!! Feel free to buy from this site as we promise you same day delivery of any purchased items. Sookh helps you to purchase any goods or service with a very few steps. If you ever find any obligation of returning any purchased item or items, please review our Return & Refund Policy.",
    "productReviews": []
  },
  {
    "productCatgoris": "womensfashion",
    "productSubCatgoris": "Kaftan",
    "productStoreName": "S-trade Store",
    "productId": "29",
    "productName": "Georgette Luxury Kaftan",
    "productImage": "https://images.pexels.com/photos/15388429/pexels-photo-15388429.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1",
    "productPrice": "699",
    "productDiscount": "10",
    "offerStatus": false,
    "productBrand": "Strade",
    "ExpressDelivary": true,
    "FreeDelivary": true,
    "productStock": "60",
    "productSold": "10",
    "offerStartAndEnDate": "12-12-2021-01-04-2022",
    "offerName": "",
    "productDescription": ["Georgette Luxury Kaftan"],
    "productSpecipication": {
      "Brand": "S-trade",
      "size": ["41", "42", "43", "45"],
      "gender": "Womens",
      "Color": ["Pink"]
    },
    "productShipping": "Dhaka city !!!! Feel free to buy from this site as we promise you same day delivery of any purchased items. Sookh helps you to purchase any goods or service with a very few steps. If you ever find any obligation of returning any purchased item or items, please review our Return & Refund Policy.",
    "productReviews": []
  },
  {
    "productCatgoris": "womensfashion",
    "productSubCatgoris": "Kaftan",
    "productStoreName": "S-trade Store",
    "productId": "30",
    "productName": "Georgette Luxury Kaftan",
    "productImage": "https://images.pexels.com/photos/15388429/pexels-photo-15388429.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1",
    "productPrice": "699",
    "productDiscount": "10",
    "offerStatus": false,
    "productBrand": "Strade",
    "ExpressDelivary": false,
    "FreeDelivary": false,
    "productStock": "60",
    "productSold": "10",
    "offerStartAndEnDate": "12-12-2021-01-04-2022",
    "offerName": "",
    "productDescription": ["Georgette Luxury Kaftan"],
    "productSpecipication": {
      "Brand": "S-trade",
      "size": ["41", "42", "43", "45"],
      "gender": "Womens",
      "Color": ["Pink"]
    },
    "productShipping": "Dhaka city !!!! Feel free to buy from this site as we promise you same day delivery of any purchased items. Sookh helps you to purchase any goods or service with a very few steps. If you ever find any obligation of returning any purchased item or items, please review our Return & Refund Policy.",
    "productReviews": []
  },
  {
    "productCatgoris": "womensfashion",
    "productSubCatgoris": "Kamiz",
    "productStoreName": "S-trade Store",
    "productId": "31",
    "productName": "Cotton Unstitched Salwar Kameez for Women - Pink",
    "productImage": "https://images.pexels.com/photos/15388429/pexels-photo-15388429.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1",
    "productPrice": "699",
    "productDiscount": "10",
    "offerStatus": false,
    "productBrand": "Strade",
    "ExpressDelivary": false,
    "FreeDelivary": false,
    "productStock": "60",
    "productSold": "10",
    "offerStartAndEnDate": "12-12-2021-01-04-2022",
    "offerName": "",
    "productDescription": ["Cotton Unstitched Salwar Kameez for Women - Pink"],
    "productSpecipication": {
      "Brand": "S-trade",
      "size": ["40", "41", "42", "43", "45"],
      "gender": "Womens",
      "Color": ["Pink"]
    },
    "productShipping": "Dhaka city !!!! Feel free to buy from this site as we promise you same day delivery of any purchased items. Sookh helps you to purchase any goods or service with a very few steps. If you ever find any obligation of returning any purchased item or items, please review our Return & Refund Policy.",
    "productReviews": []
  },
]

I have to find all products based on a given size array. That size needs to match the productSpecipication > Size[] array. if matched, it will return the particular products as a products array.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment