Repository: marpple/FxTS
Pull request #137 (2022-02-03 16:39:05)

feat: add `fromEntries` function
Hi all,
I wrote a `fromEntries` function that performs inverse of `entries` function(key-value pair iterable/asyncIterable to an object).
This function supports type inference well.

My English is not very good, so there may be some strange English descriptions.

And also, maybe my test cases are weird or poorly written.

Any advice would be welcome. 😄 

hg-pyun: This code failed to pass the test case. Please check it.
Kuhave: > This code failed to pass the test case. Please check it.

@hg-pyun I added a few more test cases as per your advice 😄 

In my opinion, the reason the test doesn't pass seems to be related to this existing issue #93 .

I confirmed that the test cases in this PR passed.

![image](https://user-images.githubusercontent.com/14926266/152646716-9ae48583-3197-4c64-83a7-cd93158999c1.png)


hg-pyun: @kuhave Oh, I see. The final approval is delegated to @shine1594 and @ppeeou 
ppeeou: Thank you for your contribution👍
I left a few comments

Patch:
diff --git a/src/Lazy/entries.ts b/src/Lazy/entries.ts
index aa402734..ec1d1699 100644
--- a/src/Lazy/entries.ts
+++ b/src/Lazy/entries.ts
@@ -12,6 +12,9 @@ type Entries<T extends Record<string, any>, K extends keyof T> = K extends any
  * [...entries({ a: 1, b: "2", c: true })]
  * // [["a", 1], ["b", "2"], ["c", true]]
  * ```
+ *
+ *
+ * see {@link https://fxts.dev/docs/fromEntries | fromEntries}
  */
 
 function* entries<T extends Record<string, any>>(
diff --git a/src/fromEntries.ts b/src/fromEntries.ts
new file mode 100644
index 00000000..64b2d549
--- /dev/null
+++ b/src/fromEntries.ts
@@ -0,0 +1,58 @@
+import reduce from "./reduce";
+import IterableInfer from "./types/IterableInfer";
+import Key from "./types/Key";
+import ReturnValueType from "./types/ReturnValueType";
+import { isAsyncIterable, isIterable } from "./_internal/utils";
+
+/**
+ * Returns an object from string keyed-value pairs.
+ *
+ * @example
+ * ```ts
+ * const arr = [
+ *     ["a", 1],
+ *     ["b", true],
+ *     ["c", "hello"],
+ *     ["d", { d1: 1, d2: 3 }],
+ * ] as (
+ *     | ["a", number]
+ *     | ["b", boolean]
+ *     | ["c", string]
+ *     | ["d", { d1: number, d2: number; }]
+ * )[];
+ * fromEntries(arr); // { a: 1, b: true, c: 'hello', d: { d1: 1, d2: 3 } }
+ * ```
+ *
+ *
+ * see {@link https://fxts.dev/docs/entries | entries}
+ */
+
+function fromEntries<
+  U extends [Key, any] | readonly [Key, any],
+  T extends Iterable<U> | AsyncIterable<U>,
+>(
+  iterable: T,
+): ReturnValueType<
+  T,
+  {
+    [K in IterableInfer<T> as K[0]]: K[1];
+  }
+>;
+function fromEntries<T extends [Key, any]>(
+  iter: Iterable<T> | AsyncIterable<T>,
+) {
+  const obj: Record<Key, any> = {};
+  const reducer = (obj: Record<Key, any>, [key, val]: T): Record<Key, any> => {
+    obj[key] = val;
+    return obj;
+  };
+  if (isAsyncIterable(iter)) {
+    return reduce(reducer, obj, iter);
+  } else if (isIterable(iter)) {
+    return reduce(reducer, obj, iter);
+  }
+
+  throw new TypeError("'iterable' must be type of Iterable or AsyncIterable");
+}
+
+export default fromEntries;
diff --git a/src/index.ts b/src/index.ts
index aa4c9952..83ddd560 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -10,6 +10,7 @@ import every from "./every";
 import evolve from "./evolve";
 import find from "./find";
 import findIndex from "./findIndex";
+import fromEntries from "./fromEntries";
 import groupBy from "./groupBy";
 import head from "./head";
 import identity from "./identity";
@@ -59,6 +60,7 @@ export {
   evolve,
   find,
   findIndex,
+  fromEntries,
   groupBy,
   head,
   head as first,
diff --git a/src/juxt.ts b/src/juxt.ts
index ef488aa5..7435d4bb 100644
--- a/src/juxt.ts
+++ b/src/juxt.ts
@@ -30,7 +30,7 @@ type JuxtReturnTypes<ARGS, FS extends Arrow[]> = ARGS extends undefined
  *
  * @example
  * ```ts
- * const range = juxt([Math.min, Math.max], [1, 2, 3, 4]); // [1, 4]
+ * const range = juxt([Math.min, Math.max])(1, 2, 3, 4); // [1, 4]
  *
  * // with pipe
  * const entries = (obj: { a: number; b: number }) =>
diff --git a/website/function.json b/website/function.json
index 29f98310..357f99c7 100644
--- a/website/function.json
+++ b/website/function.json
@@ -54,6 +54,7 @@
     "evolve",
     "find",
     "findIndex",
+    "fromEntries",
     "groupBy",
     "head",
     "identity",


Test patch:
diff --git a/test/fromEntries.spec.ts b/test/fromEntries.spec.ts
new file mode 100644
index 00000000..2a169d49
--- /dev/null
+++ b/test/fromEntries.spec.ts
@@ -0,0 +1,67 @@
+import { fromEntries, toAsync } from "../src/index";
+
+//an object from string keyed-value pairs.
+describe("fromEntries", function () {
+  describe("sync", function () {
+    it("should return proper object when iterable string keyed-value pairs is given as an argument", function () {
+      const arr = [
+        ["a", 1],
+        ["b", true],
+        ["c", "hello"],
+        ["d", { d1: 1, d2: 3 }],
+      ] as (
+        | ["a", number]
+        | ["b", true]
+        | ["c", string]
+        | ["d", { d1: number; d2: number }]
+      )[];
+      const res = fromEntries(arr);
+      expect(res).toEqual({ a: 1, b: true, c: "hello", d: { d1: 1, d2: 3 } });
+    });
+    it("should return proper object when ES6 Map is given as an argument", function () {
+      const sym = Symbol();
+      const sym2 = Symbol();
+      const arr: [
+        string | number | symbol,
+        number | string | { a1: number; b1: string },
+      ][] = [
+        ["a", 1],
+        ["b", "hello"],
+        [10, 1000],
+        [20, 2000],
+        [sym, "symbol"],
+        [sym2, { a1: 100, b1: "hello symbol" }],
+        ["obj", { a1: 10, b1: "hello object" }],
+      ];
+      const map = new Map(arr);
+      const res = fromEntries(map);
+      expect(res).toEqual({
+        10: 1000,
+        20: 2000,
+        a: 1,
+        b: "hello",
+        obj: { a1: 10, b1: "hello object" },
+        [sym]: "symbol",
+        [sym2]: { a1: 100, b1: "hello symbol" },
+      });
+    });
+  });
+
+  describe("async", function () {
+    it("should return proper object when asyncIterable string keyed-value pairs is given as an argument", async function () {
+      const arr = [
+        ["a", 1],
+        ["b", true],
+        ["c", "hello"],
+        ["d", { d1: 1, d2: 3 }],
+      ] as (
+        | ["a", number]
+        | ["b", true]
+        | ["c", string]
+        | ["d", { d1: number; d2: number }]
+      )[];
+      const res = await fromEntries(toAsync(arr));
+      expect(res).toEqual({ a: 1, b: true, c: "hello", d: { d1: 1, d2: 3 } });
+    });
+  });
+});
diff --git a/type-check/fromEntries.test.ts b/type-check/fromEntries.test.ts
new file mode 100644
index 00000000..defa4a56
--- /dev/null
+++ b/type-check/fromEntries.test.ts
@@ -0,0 +1,37 @@
+import * as Test from "../src/types/Test";
+import { fromEntries, entries, pipe, toAsync } from "../src";
+
+const { checks, check } = Test;
+
+const obj = {
+  a: 1,
+  b: true,
+  c: "hello",
+  d: { d1: 1, d2: 3 },
+};
+
+const arr = [
+  ["a", 1],
+  ["b", true],
+  ["c", "hello"],
+  ["d", { d1: 1, d2: 3 }],
+] as (
+  | ["a", number]
+  | ["b", boolean]
+  | ["c", string]
+  | ["d", { d1: number; d2: number }]
+)[];
+
+const res1 = fromEntries(arr);
+const res2 = pipe(obj, entries, fromEntries);
+const res3 = pipe(obj, entries, (iter) => fromEntries(iter));
+const res4 = pipe(obj, entries, toAsync, fromEntries);
+const res5 = pipe(obj, entries, toAsync, (iter) => fromEntries(iter));
+
+checks([
+  check<typeof res1, typeof obj, Test.Pass>(),
+  check<typeof res2, typeof obj, Test.Pass>(),
+  check<typeof res3, typeof obj, Test.Pass>(),
+  check<typeof res4, Promise<typeof obj>, Test.Pass>(),
+  check<typeof res5, Promise<typeof obj>, Test.Pass>(),
+]);