Skip to content

Instantly share code, notes, and snippets.

@OliverJAsh
Created January 3, 2019 15:14
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save OliverJAsh/acafba4f099f6e677dbb0a38c60dc33d to your computer and use it in GitHub Desktop.
Save OliverJAsh/acafba4f099f6e677dbb0a38c60dc33d to your computer and use it in GitHub Desktop.
TypeScript object filter
const tuple = <T extends Array<unknown>>(...args: T): T => args;
// `Object.keys` does not return the keys as string literals, only strings. Use this helper as a
// workaround. https://github.com/Microsoft/TypeScript/pull/12253#issuecomment-263132208
const keys = <O extends object>(obj: O) => Object.keys(obj) as Array<keyof O>;
// `Object.entries` is ES2017, so we must define our own version.
const entries = <K extends string, V>(obj: Record<K, V>) =>
keys(obj).map(key => tuple(key, obj[key]));
const fromEntries = <K extends string, V>(arr: Array<[K, V]>) =>
// `Object.assign` is poorly typed: it returns `any` when spreading. Use cast to workaround.
Object.assign({}, ...arr.map(([k, v]) => ({ [k]: v }))) as Record<K, V>;
// Inspired by https://stackoverflow.com/a/37616104/5932012
export const filter = <K extends string, V, Result extends V>(
obj: Record<K, V>,
predicate: (key: K, value: V) => value is Result,
) =>
fromEntries(
entries(obj).filter(
(entry): entry is [K, Result] => {
const [key, value] = entry;
return predicate(key, value);
},
),
);
@janhesters
Copy link

janhesters commented Feb 6, 2019

How would you use this?

I had to use it like this (casted as any to get all errors to go away):

import { filterObject } from './filterObject';

describe('filterObject', () => {
  describe('given an object', () => {
    const given = {
      bye: {
        info: 'bye',
        t: false
      },
      hello: {
        info: 'hello',
        t: true
      },
      lol: {
        info: 'lol',
        x: true
      }
    };

    it('returns a new object', () => {
      const actual = filterObject(given, (() => true) as any);
      const expected = false;
      expect(actual === given).toEqual(expected);
    });

    it('returns the new object without the keys where the predicate is false', () => {
      const actual = filterObject(given, ((key: any, value: any) => value.t) as any);
      const expected = {
        hello: {
          info: 'hello',
          t: true
        }
      };
      expect(actual).toEqual(expected);
    });
  });
});

@IrvingArmenta
Copy link

Please explain the usage, I keep getting the boolean' must be a type predicate. error when trying to use as Array.filter works, that is returning a boolean, If I set everything as any like above it works though, but I don't think we should resort to that.

@OliverJAsh
Copy link
Author

I keep getting the boolean' must be a type predicate. error when trying to use as Array.filter works, that is returning a boolean

Sounds like you need to add an overload that supports boolean returning predicates. At the moment it is typed to support just user-defined type guards.

@IrvingArmenta
Copy link

Could you please show an example of a user-defined type guard and the usage with the filter? my typescript skills are still quite basic
Very appreciated 🙇

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