Skip to content
jericirenej edited this page May 29, 2022 · 9 revisions

Object filtering

Easily filter object by their properties - then get the filtered object back!

  • Two types of filtering available:
    • Exclude all of the matched properties or
    • Include only the matched properties.
  • Filter by a list of property names or regex filters.
  • Recursive filtering option.
    • Recursive filtering will not inspect (filter within) arrays, sets, maps, dates, and other in-built classes.
  • Compatibility with ES6 and CommonJS imports.

What's new in version 1.3

  • Improved inclusive filtering: you no longer have to specify all the properties that lead to the target property to be included. Specify only those that you want to keep and the algorithm does the rest!
  • Filtering is now recursive by default, if not specified otherwise.
  • Compatibility with ES6 and CommonJS module systems.
  • Completely reworked filtering logic.

Usage

  • Install the package via npm install @jericirenej/object-filter.
    • You can also clone the repo and transpile the files manually (npm install, followed by npm run compile which will transpile to dist).
  • Import or require the objectFilter function from @jericirenej/object-filter and pass it the appropriate configuration object.
// ES6 import
import objectFilter from "@jericirenej/object-filter"

// CommonJS import
const objectFilter = require("@jericirenej/object.filter").default;

/*
objectFilter signature: {
  targetObject: Record<string,any>,
  filters?: string|string[],
  regexFilters?: string|RegExp|(string|RegExp)[], --- At least one of the filter groups needs to be valid.
  filterType?: "exclude"|"include",               --- defaults to exclude.
  recursive?:boolean,                             --- defaults to true.

}
*/

const employeeInfo = {
  name: "John",
  surname: "Doe",
  personalInfo: {
    age: 30,
    sensitive1: "secret",
  },
  sensitive2: "secret",
};

const excludeSensitive = {
  targetObject: employeeInfo,
  regexFilters: /sensitive/,
  filterType: "exclude",
  recursive: true,
};

const cleanedEmployeeInfo = objectFilter(excludeSensitive);
// Will return
{
  name: "John",
  surname: "Doe",
  personalInfo: {
    age: 30,
  },
};

const includeSensitive = {
  targetObject: employeeInfo,
  regexFilters: /sensitive/,
  filterType: "include",
  recursive: true,
}

const sensitiveEmployeeInfo = objectFilter(includeSensitive);

//Will return
{
  sensitive2: "secret",
  personalInfo: {
    sensitive1: "secret,
  }
}

More details about the logic of exclusive and inclusive filter types

Exclusive filter type

  • At each filter level, keeps all of the properties that do not match any of the provided filters.
  • Nested properties are then filtered again upon recursive calls.

Inclusive filter type

Can be summed up as being conditionally greedy / granular.

  • It will by default include the complete value of the matched property. So, if a property is itself an object, it will include that complete sub-object.
  • However, in recursive contexts:
    • If any of the nested properties themselves match, then only the matched properties will be included.
    • If none of the nested properties match, then all of them will remain included.

In other words: At a single level, only matched properties are included, but they are included in a comprehensive way. This is then replayed down each level. The more filters you supply, the more granular and selective your included output will be.

For example, let's say we have the following object:

{
  unmatchedProp1: "primitive",
  unmatchedProp2: "primitive",
  targetProp: {
    potentiallyMatched: "primitive",
    unmatchedProp3: "primitive"
  }
}

If we run objectFilter with "include" filterType and only the targetProp filter, the return will be:

{
  targetProp: {
    potentiallyMatched: "primitive",
    unmatchedProp3: "primitive"
  }
}

However, if we run it with the ["targetProp", "potentiallyMatched"] filter (or equivalent regexFilters), the return will be:

{
  targetProp: {
    potentiallyMatched: "primitive",
  }
}

Note about non-recursive filtering

  • Non-recursive filtering only checks the top-level object properties ad will return those that pass the checks. This means, that if some of the object properties are themselves objects, they can contain properties that should have been filtered out.
  • Also, since assignment takes place at the top-level only, this means that all of the property values from the returned object, that are not primitives, should be considered shallow. Modifying them will modify the original object as well.

Object type properties that are not filtered:

Primitives and excluded object types are not subject to filtering.

  • The following types fall under primitives: string, number, boolean, bigint, symbol, undefined, null.
    • While typeof null returns an object, it is specified as a primitive in the ECMAScript docs and it also doesn't have any properties to filter by.
  • The following are considered excluded types: Array, Map, Set, Date, WeakMap, WeakSet, Int8Array, Int16Array, Int32Array, Uint8Array, Uint16Array, Uint32Array, Uint8ClampedArray, BigInt64Array, BigUint64Array, ArrayBuffer, SharedArrayBuffer

Other things to note

  • Although the filterObject function creates a new object based on the old one, it is not guaranteed to produce a deep clone of the object. In particular, excluded types (for example arrays, sets, maps, etc.) will be shallow copied. If you need to ensure immutability, use an appropriate library (lodash, immer), to clone the result.
  • You need to supply at least one type of valid filter groups. If none of the supplied filter groups are valid, the original object will be returned.
    • You can supply both filter types if you want, combining string based filters with regex patterns.
    • String based filters, passed under the filters argument, are implemented by running an include method on the source object property names (so more inclusive than a strict equality comparison).
  • If you are working with TypeScript, make sure to type cast the returned object, as its type will be Record<string,any> by default.

Planned additional features

  • Tranpoline recursive calls (could be useful for very deeply nested objects so as not to exceed the maximum call stack).
  • GitHub Page for documentation with a live code playground for testing out the package.