Repository: marpple/FxTS (ts)

Issue:
Add `omitBy` and `pickBy` function
# Suggestion

<!--
  Please fill in each section completely. Thank you!
-->

## ⭐ Suggestion

<!-- What would you like to be added? -->

## 💻 Use Cases

```ts
pickBy(
  ([key, value]) => key === 'b' || typeof value === 'boolean',
  { a: 1, b: '2', c: true }
);
// { b: '2', c: true }

await pickBy(
  async ([key, value]) => key === 'b' || typeof value === 'boolean',
  { a: 1, b: '2', c: true }
);

pipe(
  { a: 1, b: '2', c: true },
  pickBy(([key, value]) => key === 'b' || typeof value === 'boolean')
);

await pipe(
  { a: 1, b: '2', c: true },
  pickBy(async ([key, value]) => key === 'b' || typeof value === 'boolean')
);
```

```ts
omitBy(
  ([key, value]) => key === 'b' || typeof value === 'boolean',
  { a: 1, b: '2', c: true }
);
// { a: 1 }

await omitBy(
  async ([key, value]) => key === 'b' || typeof value === 'boolean',
  { a: 1, b: '2', c: true }
);

pipe(
  { a: 1, b: '2', c: true },
  omitBy(([key, value]) => key === 'b' || typeof value === 'boolean')
);

await pipe(
  { a: 1, b: '2', c: true },
  omitBy(async ([key, value]) => key === 'b' || typeof value === 'boolean')
);
```

<!-- What do you want to use this for? -->


Pull request description:
Feature/45
Fixes #45


Patch:
diff --git a/src/index.ts b/src/index.ts
index 353adfcb..6224ac75 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -18,8 +18,10 @@ import last from "./last";
 import not from "./not";
 import nth from "./nth";
 import omit from "./omit";
+import omitBy from "./omitBy";
 import partition from "./partition";
 import pick from "./pick";
+import pickBy from "./pickBy";
 import pipe from "./pipe";
 import pipe1 from "./pipe1";
 import reduce from "./reduce";
@@ -50,8 +52,10 @@ export {
   not,
   nth,
   omit,
+  omitBy,
   partition,
   pick,
+  pickBy,
   pipe,
   pipe1,
   reduce,
diff --git a/src/omitBy.ts b/src/omitBy.ts
new file mode 100644
index 00000000..d43dd48b
--- /dev/null
+++ b/src/omitBy.ts
@@ -0,0 +1,102 @@
+import {
+  AsyncEntryPredicate,
+  ConditionalAsyncEntryPredicate,
+  EntryPredicate,
+} from "./types/EntryPredicate";
+import toArray from "./toArray";
+import reject from "./Lazy/reject";
+import toAsync from "./Lazy/toAsync";
+import pipe from "./pipe";
+import { map, zip } from "./Lazy";
+
+/**
+ *
+ * Returns a partial copy of an object containing only the keys that satisfy the supplied predicate.
+ *
+ * @example
+ * ```ts
+ * const obj = { a: 1, b: "2", c: true };
+ * omitBy(([key, value]) => key === "a" || value === true, obj); // { b: "2" }
+ *
+ * // asynchronous predicate
+ * await omitBy(async ([key, value]) => key === "a" || value === true, obj); // { b: "2" }
+ *
+ * // Using with the `pipe` function
+ * pipe(
+ *   obj,
+ *   omitBy(([key, value]) => key === "a" || value === true)
+ * );
+ *
+ * await pipe(
+ *   obj,
+ *   omitBy(async ([key, value]) => key === "a" || value === true)
+ * );
+ * ```
+ * see {@link https://fxts.dev/docs/pipe | pipe}, {@link https://fxts.dev/docs/omit | omit},
+ */
+
+function omitBy<T extends object, F extends AsyncEntryPredicate<T>>(
+  f: F,
+  obj: T,
+): Promise<Partial<T>>;
+
+function omitBy<T extends object, F extends AsyncEntryPredicate<T>>(
+  f: F,
+): (obj: T) => Promise<Partial<T>>;
+
+function omitBy<T extends object, F extends EntryPredicate<T>>(
+  f: F,
+  obj: T,
+): Partial<T>;
+
+function omitBy<T extends object, F extends EntryPredicate<T>>(
+  f: F,
+): (obj: T) => Partial<T>;
+
+function omitBy<T extends object, F extends ConditionalAsyncEntryPredicate<T>>(
+  f: F,
+  obj: T,
+): Partial<T> | Promise<Partial<T>>;
+
+function omitBy<T extends object, F extends ConditionalAsyncEntryPredicate<T>>(
+  f: F,
+): (obj: T) => Partial<T> | Promise<Partial<T>>;
+
+function omitBy<T extends object, F extends ConditionalAsyncEntryPredicate<T>>(
+  f: F,
+  obj?: T,
+):
+  | Partial<T>
+  | Promise<Partial<T>>
+  | ((obj: T) => Partial<T>)
+  | ((obj: T) => Promise<Partial<T>>)
+  | ((obj: T) => Partial<T> | Promise<Partial<T>>) {
+  if (obj === undefined) {
+    return (obj: T) => omitBy(f, obj);
+  }
+
+  const entries = Object.entries(obj);
+  const conditions = entries.map((entry) => f(entry as any));
+  const isAsync = conditions.some((c) => c instanceof Promise);
+
+  if (isAsync) {
+    return pipe(
+      entries,
+      zip(toAsync(conditions)),
+      reject(([cond]) => cond),
+      map(([, entry]) => entry),
+      toArray,
+      Object.fromEntries,
+    ) as Promise<Partial<T>>;
+  } else {
+    return pipe(
+      entries,
+      zip(conditions),
+      reject(([cond]) => cond),
+      map(([, entry]) => entry),
+      Object.fromEntries,
+    ) as Partial<T>;
+  }
+}
+
+export default omitBy;
diff --git a/src/pickBy.ts b/src/pickBy.ts
new file mode 100644
index 00000000..a43d09fa
--- /dev/null
+++ b/src/pickBy.ts
@@ -0,0 +1,102 @@
+import {
+  AsyncEntryPredicate,
+  ConditionalAsyncEntryPredicate,
+  EntryPredicate,
+} from "./types/EntryPredicate";
+import toArray from "./toArray";
+import filter from "./Lazy/filter";
+import toAsync from "./Lazy/toAsync";
+import pipe from "./pipe";
+import { map, zip } from "./Lazy";
+
+/**
+ *
+ * Returns a partial copy of an object containing only the keys that satisfy the supplied predicate.
+ *
+ * @example
+ * ```ts
+ * const obj = { a: 1, b: "2", c: true };
+ * pickBy(([key, value]) => key === "a" || value === true, obj); // { a: 1, c: true }
+ *
+ * // asynchronous predicate
+ * await pickBy(async ([key, value]) => key === "a" || value === true, obj); // { a: 1, c: true }
+ *
+ * // Using with the `pipe` function
+ * pipe(
+ *   obj,
+ *   pickBy(([key, value]) => key === "a" || value === true)
+ * );
+ *
+ * await pipe(
+ *   obj,
+ *   pickBy(async ([key, value]) => key === "a" || value === true)
+ * );
+ * ```
+ * see {@link https://fxts.dev/docs/pipe | pipe}, {@link https://fxts.dev/docs/pick | pick},
+ */
+
+function pickBy<T extends object, F extends AsyncEntryPredicate<T>>(
+  f: F,
+  obj: T,
+): Promise<Partial<T>>;
+
+function pickBy<T extends object, F extends AsyncEntryPredicate<T>>(
+  f: F,
+): (obj: T) => Promise<Partial<T>>;
+
+function pickBy<T extends object, F extends EntryPredicate<T>>(
+  f: F,
+  obj: T,
+): Partial<T>;
+
+function pickBy<T extends object, F extends EntryPredicate<T>>(
+  f: F,
+): (obj: T) => Partial<T>;
+
+function pickBy<T extends object, F extends ConditionalAsyncEntryPredicate<T>>(
+  f: F,
+  obj: T,
+): Partial<T> | Promise<Partial<T>>;
+
+function pickBy<T extends object, F extends ConditionalAsyncEntryPredicate<T>>(
+  f: F,
+): (obj: T) => Partial<T> | Promise<Partial<T>>;
+
+function pickBy<T extends object, F extends ConditionalAsyncEntryPredicate<T>>(
+  f: F,
+  obj?: T,
+):
+  | Partial<T>
+  | Promise<Partial<T>>
+  | ((obj: T) => Partial<T>)
+  | ((obj: T) => Promise<Partial<T>>)
+  | ((obj: T) => Partial<T> | Promise<Partial<T>>) {
+  if (obj === undefined) {
+    return (obj: T) => pickBy(f, obj);
+  }
+
+  const entries = Object.entries(obj);
+  const conditions = entries.map((entry) => f(entry as any));
+  const isAsync = conditions.some((c) => c instanceof Promise);
+
+  if (isAsync) {
+    return pipe(
+      entries,
+      zip(toAsync(conditions)),
+      filter(([cond]) => cond),
+      map(([, entry]) => entry),
+      toArray,
+      Object.fromEntries,
+    ) as Promise<Partial<T>>;
+  } else {
+    return pipe(
+      entries,
+      zip(conditions),
+      filter(([cond]) => cond),
+      map(([, entry]) => entry),
+      Object.fromEntries,
+    ) as Partial<T>;
+  }
+}
+
+export default pickBy;
diff --git a/src/types/EntryPredicate.ts b/src/types/EntryPredicate.ts
new file mode 100644
index 00000000..b57a2d40
--- /dev/null
+++ b/src/types/EntryPredicate.ts
@@ -0,0 +1,14 @@
+export type AsyncEntryPredicate<T extends object> = ([key, value]: [
+  keyof T,
+  T[keyof T],
+]) => Promise<boolean>;
+
+export type ConditionalAsyncEntryPredicate<T extends object> = ([key, value]: [
+  keyof T,
+  T[keyof T],
+]) => boolean | Promise<boolean>;
+
+export type EntryPredicate<T extends object> = ([key, value]: [
+  keyof T,
+  T[keyof T],
+]) => boolean;


Test patch:
diff --git a/test/omitBy.spec.ts b/test/omitBy.spec.ts
new file mode 100644
index 00000000..cdfaeb9b
--- /dev/null
+++ b/test/omitBy.spec.ts
@@ -0,0 +1,62 @@
+import { omitBy, pipe } from "../src/index";
+
+describe("omitBy", function () {
+  describe("sync", function () {
+    const obj = { a: 1, b: "2", c: true };
+
+    it.each([
+      [
+        ([key, value]: [string, any]) =>
+          key === "b" || typeof value === "boolean",
+        obj,
+      ],
+      [
+        ([key, value]: [string, any]) =>
+          key === "b" ||
+          typeof value === "boolean" ||
+          (typeof value === "symbol" ? Promise.resolve(true) : false),
+        obj,
+      ],
+    ])("should be omitted properties by given predicate function", (f, obj) => {
+      expect(omitBy(f, obj)).toEqual({ a: 1 });
+    });
+
+    it("should be able to be used as a curried function in the pipeline", function () {
+      const res = pipe(
+        { a: 1, b: "2", c: true },
+        omitBy(([key, value]) => key === "b" || typeof value === "boolean"),
+      );
+      expect(res).toEqual({ a: 1 });
+    });
+  });
+
+  describe("async", function () {
+    const obj = { a: 1, b: "2", c: true };
+    it.each([
+      [
+        async ([key, value]: [string, any]) =>
+          key === "b" || typeof value === "boolean",
+        obj,
+      ],
+      [
+        ([key, value]: [string, any]) =>
+          key === "b" || Promise.resolve(typeof value === "boolean"),
+        obj,
+      ],
+    ])(
+      "should be omitted properties by given predicate function",
+      async (f, obj) => {
+        expect(await omitBy(f, obj)).toEqual({ a: 1 });
+      },
+    );
+    it("should be able to be used as a curried function in the pipeline", async function () {
+      const res = await pipe(
+        obj,
+        omitBy(
+          async ([key, value]) => key === "b" || typeof value === "boolean",
+        ),
+      );
+      expect(res).toEqual({ a: 1 });
+    });
+  });
+});
diff --git a/test/pickBy.spec.ts b/test/pickBy.spec.ts
new file mode 100644
index 00000000..9097de6a
--- /dev/null
+++ b/test/pickBy.spec.ts
@@ -0,0 +1,62 @@
+import { pickBy, pipe } from "../src/index";
+
+describe("pickBy", function () {
+  describe("sync", function () {
+    const obj = { a: 1, b: "2", c: true };
+
+    it.each([
+      [
+        ([key, value]: [string, any]) =>
+          key === "b" || typeof value === "boolean",
+        obj,
+      ],
+      [
+        ([key, value]: [string, any]) =>
+          key === "b" ||
+          typeof value === "boolean" ||
+          (typeof value === "symbol" ? Promise.resolve(true) : false),
+        obj,
+      ],
+    ])("should be picked properties by given predicate function", (f, obj) => {
+      expect(pickBy(f, obj)).toEqual({ b: "2", c: true });
+    });
+
+    it("should be able to be used as a curried function in the pipeline", function () {
+      const res = pipe(
+        { a: 1, b: "2", c: true },
+        pickBy(([key, value]) => key === "b" || typeof value === "boolean"),
+      );
+      expect(res).toEqual({ b: "2", c: true });
+    });
+  });
+
+  describe("async", function () {
+    const obj = { a: 1, b: "2", c: true };
+    it.each([
+      [
+        async ([key, value]: [string, any]) =>
+          key === "b" || typeof value === "boolean",
+        obj,
+      ],
+      [
+        ([key, value]: [string, any]) =>
+          key === "b" || Promise.resolve(typeof value === "boolean"),
+        obj,
+      ],
+    ])(
+      "should be picked properties by given predicate function",
+      async (f, obj) => {
+        expect(await pickBy(f, obj)).toEqual({ b: "2", c: true });
+      },
+    );
+    it("should be able to be used as a curried function in the pipeline", async function () {
+      const res = await pipe(
+        obj,
+        pickBy(
+          async ([key, value]) => key === "b" || typeof value === "boolean",
+        ),
+      );
+      expect(res).toEqual({ b: "2", c: true });
+    });
+  });
+});
diff --git a/type-check/omitBy.test.ts b/type-check/omitBy.test.ts
new file mode 100644
index 00000000..04f7c6d2
--- /dev/null
+++ b/type-check/omitBy.test.ts
@@ -0,0 +1,25 @@
+import * as Test from "../src/types/Test";
+import { omitBy } from "../src";
+
+const { checks, check } = Test;
+
+const obj = {
+  a: 1,
+  b: "2",
+  c: true,
+};
+
+const res1 = omitBy(([key, value]) => key === "a" || value === true, obj);
+const res2 = omitBy(async ([key, value]) => key === "a" || value === true, obj);
+const res3 = omitBy(
+  ([key, value]) => key === "a" || Promise.resolve(value === true),
+  obj,
+);
+
+type Res = { a?: number; b?: string; c?: boolean };
+
+checks([
+  check<typeof res1, Res, Test.Pass>(),
+  check<typeof res2, Promise<Res>, Test.Pass>(),
+  check<typeof res3, Res | Promise<Res>, Test.Pass>(),
+]);
diff --git a/type-check/pickBy.test.ts b/type-check/pickBy.test.ts
new file mode 100644
index 00000000..6212861d
--- /dev/null
+++ b/type-check/pickBy.test.ts
@@ -0,0 +1,25 @@
+import * as Test from "../src/types/Test";
+import { pickBy } from "../src";
+
+const { checks, check } = Test;
+
+const obj = {
+  a: 1,
+  b: "2",
+  c: true,
+};
+
+const res1 = pickBy(([key, value]) => key === "a" || value === true, obj);
+const res2 = pickBy(async ([key, value]) => key === "a" || value === true, obj);
+const res3 = pickBy(
+  ([key, value]) => key === "a" || Promise.resolve(value === true),
+  obj,
+);
+
+type Res = { a?: number; b?: string; c?: boolean };
+
+checks([
+  check<typeof res1, Res, Test.Pass>(),
+  check<typeof res2, Promise<Res>, Test.Pass>(),
+  check<typeof res3, Res | Promise<Res>, Test.Pass>(),
+]);