list-fns
v1.4.0
Published
A collection of utility functions to be used with .map, .filter, .sort and .reduce
Downloads
630
Readme
list-fns
This library contains higher order functions that simplify common list operations, similar to what you'd find in lodash
or ramda
. Unlike these libraries, list-fns
is designed specifically to be used with the native array methods.
These functions have not been rigorously tested for performance so they are currently not recommended for use with large datasets.
Example
import { byProperty, get, uniqueByProperty } from "list-fns";
const people = [
{ name: "Jack", age: 44 },
{ name: "Jack", age: 60 },
{ name: "Jane", age: 20 },
];
// Inline implementation:
people
.filter(
(person, index) => index === people.findIndex(p => p.name === person.name)
)
.sort((a, b) => (a.age < b.age ? -1 : a.age > b.age ? 1 : 0))
.map(person => person.name); // ["Jane", "Jack"]
// With list functions:
people
.filter(uniqueByProperty("name"))
.sort(byProperty("age"))
.map(get("name")); // ["Jane", "Jack"]
Install
npm install list-fns
A note about sorting
This library contains functions to be used with [].sort()
. Always be mindful of the fact that .sort()
and .reverse()
will mutate the original list. If .sort()
is the first method you're calling on a list you should probably clone it first in order to avoid unexpected behavior:
[...list].sort();
list.slice().sort();
[].concat(list).sort();
Functions
by
by: <T>(func: (el: T) => any) => (a: T, b: T) => 0 | 1 | -1
Use with: sort
Sort the elements by func(element)
. Supports sorting by boolean values (elements that are true
first).
[{ a: 2 }, { a: 1 }].sort(by(el => el.a)); // Returns [{ a: 1 }, { a: 2 }]
const by = <T>(func: (el: T) => any) => (a: T, b: T) => {
const A = func(a),
B = func(b);
if (typeof A === "boolean") return A && !B ? -1 : !A && B ? 1 : 0;
return A < B ? -1 : A > B ? 1 : 0;
}
byProperty
byProperty: <TObject extends object, TKey extends keyof TObject>(key: TKey) => (a: TObject, b: TObject) => 0 | 1 | -1
Use with: sort
Sort the elements by element[key]
(can also be an array index). Supports sorting by boolean values (elements that are true
first).
[{ a: 2 }, { a: 1 }].sort(byProperty('a')); // Returns [{ a: 1 }, { a: 2 }]
[["a", 2], ["a", 1]].sort(byProperty(1)); // Returns [["a", 1], ["a", 2]]
const byProperty = <TObject extends object, TKey extends keyof TObject>(
key: TKey
) => by<TObject>(get(key))
byValue
byValue: (a: number, b: number) => 0 | 1 | -1
Use with: sort
Sort a list of numbers. This is useful because javascript sorts numbers as string, meaning that [25, 100] results in [100, 25] since "2" is greater than "1"
[100, 25].sort(); // Returns [100, 25]
[100, 25].sort(byValue); // Returns [25, 100]
const byValue = (a: number, b: number) => (a < b ? -1 : a > b ? 1 : 0)
countBy
countBy: <T>(func: (el: T) => boolean) => (acc: number, el: T) => number
Use with: reduce
Returns the number of times func
returned true
for the list elements. A number must be passed to the second argument of reduce
. Can be combined with boolean-returning functions like is
, isnt
, propertyIs
or propertyIsOneOf
.
["a", "a", "b"].reduce(countBy(el => el === "a"), 0); // Returns 2
["a", "a", "b"].reduce(countBy(is("a")), 0); // Returns 2
const countBy = <T>(func: (el: T) => boolean) => (acc: number, el: T) =>
acc + (func(el) ? 1 : 0)
duplicates
duplicates: (el: unknown, _: number, list: unknown[]) => boolean
Use with: filter
Returns duplicates
[1, 1, 1, 2].filter(duplicates); // Returns [1, 1, 1]
const duplicates = duplicatesBy(el => el)
duplicatesBy
duplicatesBy: <T>(func: (el: T) => unknown) => (el: T, _: number, list: T[]) => boolean
Use with: filter
Returns all duplicates compared by func(element)
[{ a: 1 }, { a : 1 }, { a: 2 }].filter(duplicatesBy(el => el.a)); // Returns [{ a: 1 }, { a: 1 }]
const duplicatesBy = <T>(func: (el: T) => unknown) => (
el: T,
_: number,
list: T[]
) => {
let n = 0;
for (let i = 0; i < list.length; i++) {
if (n >= 2) return true;
if (func(list[i]) === func(el)) n++;
}
return false;
}
duplicatesByProperty
duplicatesByProperty: <TObject extends object, TKey extends keyof TObject>(key: TKey) => (el: TObject, _: number, list: TObject[]) => boolean
Use with: filter
Returns duplicates compared by element[key]
[{ a: 1 }, { a: 1 }].filter(duplicatesByProperty('a')); // Return [{ a: 1 }, { a: 1 }]
const duplicatesByProperty = <
TObject extends object,
TKey extends keyof TObject
>(
key: TKey
) => duplicatesBy<TObject>(get(key))
exclude
exclude: <T>(list: T[]) => (el: T) => boolean
Use with: filter
Removes the provided elements from the list
[1, 2, 3, 4].filter(exclude([1, 2])); // Returns [3, 4]
const exclude = <T>(list: T[]) => (el: T) =>
findIndex(list, a => a === el) === -1
excludeBy
excludeBy: <T>(func: (el: T) => unknown, list: T[]) => (el: T) => boolean
Use with: filter
Removes the provided elements from the list compared by running func
on elements in both lists
[{ a: 1 }, { a: 2 }, { a: 3 }, { a: 4 }]
.filter(excludeBy(el => el.a, [{ a: 1 }, { a: 2 }]));
// Returns [{ a: 3 }, { a: 4 }]
const excludeBy = <T>(func: (el: T) => unknown, list: T[]) => (el: T) =>
findIndex(list, a => func(a) === func(el)) === -1
excludeByProperty
excludeByProperty: <TObject extends object, TKey extends keyof TObject>(key: TKey, list: TObject[]) => (el: TObject) => boolean
Use with: filter
Removes the provided elements from the list compared at key
[{ a: 1 }, { a: 2 }, { a: 3 }, { a: 4 }]
.filter(excludeByProperty('a', [{ a: 1 }, { a: 2 }]));
// Returns [{ a: 3 }, { a: 4 }]
const excludeByProperty = <
TObject extends object,
TKey extends keyof TObject
>(
key: TKey,
list: TObject[]
) => excludeBy(get(key), list)
get
get: {
<TObject extends object, TKey1 extends keyof TObject, TKey2 extends keyof TObject[TKey1], TKey3 extends keyof TObject[TKey1][TKey2]>(key1: TKey1, key2: TKey2, key3: TKey3): (obj: TObject) => TObject[TKey1][TKey2][TKey3];
<TObject extends object, TKey1 extends keyof TObject, TKey2 extends keyof TObject[TKey1]>(key1...;
}
Use with map
or filter
Returns element[key]
(can also be an array index). Supports up to three keys of depth.
[{ a: 1 }, { a: 2 }].map(get('a')); // Returns [1, 2]
[["a", 1], ["a", 2]].map(get(1)); // Returns [1, 2]
[{ a: { b: { c: 1 } } }].map(get('a', 'b', 'c')); // Returns [1]
export function get<
TObject extends object,
TKey1 extends keyof TObject,
TKey2 extends keyof TObject[TKey1],
TKey3 extends keyof TObject[TKey1][TKey2]
>(key1: TKey1, key2?: TKey2, key3?: TKey3) {
return (obj: TObject) => {
if (key3 && key2)
return obj && obj[key1] && obj[key1][key2] && obj[key1][key2][key3];
if (key2) return obj && obj[key1] && obj[key1][key2];
return obj && obj[key1];
};
}
groupBy
groupBy: <K extends string, V>(func: (el: V) => K | undefined) => (acc: Record<K, V[]>, el: V) => Record<K, V[]>
Use with: reduce
Given a key-returning function, returns the elements grouped in an object according to the returned keys. A second argument must be passed to reduce
. For javascript an empty object is enough. For typescript an object with properties or a type cast may be required.
[{ age: 10 }, { age: 80 }].reduce(
groupBy(el => (el.age > 30 ? "old" : "young")),
{ old: [], young: [] }
); // Returns { old: [{ age: 80 }], young: [{ age: 10 }]}
const groupBy = <K extends string, V>(
func: (el: V) => K | undefined
) => (acc: Record<K, V[]>, el: V): Record<K, V[]> => {
const groupName = func(el);
if (!groupName) return acc;
if (!acc[groupName]) acc[groupName] = [];
acc[groupName].push(el);
return acc;
}
groupByMany
groupByMany: <K extends string, V>(func: (el: V) => K[] | undefined) => (acc: Record<K, V[]>, el: V) => Record<K, V[]>
Use with: reduce
Given a function func
that returns a list of keys, returns an object containing the elements grouped by the returned keys. Unlike the groupBy
function, elements can appear several times in this object. Good for grouping objects by properties that are arrays. An empty object must be passed as the second argument to reduce
const b1: B = { items: ["a", "b"] };
const b2: B = { items: ["a"] };
[b1, b2].reduce(groupByMany(b => b.items), {});
// Returns { a: [{ items: ["a", "b"] }, { items: ["a"] }], b: [{ items: ["b"] }] }
const groupByMany = <K extends string, V>(
func: (el: V) => K[] | undefined
) => (acc: Record<K, V[]>, el: V): Record<K, V[]> => {
const groupNames = func(el) || [];
groupNames.forEach(key => {
if (!acc[key]) acc[key] = [];
acc[key].push(el);
});
return acc;
}
groupByProperty
groupByProperty: <K extends keyof V, V extends { [key: string]: any; }>(key: K) => (acc: Record<V[K], V[]>, el: V) => Record<V[K], V[]>
Use with: reduce
Given a property name, returns an object containing the elements grouped by the values for that property. A second argument must be passed to reduce
. For javascript an empty object is enough. For typescript an object with properties or a type cast may be required.
[{ name: "Jane" }, { name: "John" }].reduce(
groupByProperty("name"),
{}
); // Returns { Jane: [{ name: "Jane" }], John: [{ name: "John" }] }
const groupByProperty = <
K extends keyof V,
V extends { [key: string]: any }
>(
key: K
) => (acc: Record<V[K], V[]>, el: V): Record<V[K], V[]> => {
const groupName = el[key];
if (!groupName) return acc;
if (!acc[groupName]) acc[groupName] = [];
acc[groupName].push(el);
return acc;
}
has
has: <TObject extends object, TKey extends keyof TObject>(...keys: TKey[]) => (object: TObject) => object is TObject & HasProperties<TObject, TKey>
Use with: find
, filter
Returns true
for elements where element[key]
for all provided keys is defined. This is useful when properties are needed but optional in the element type.
Known limitations: Type inference doesn't always work when list elements have an inferred type.
type Person = { name?: string };
const people: Person[] = [{ name: "John" }, {}];
people.filter(has("name")); // Returns [{ name: "a" }]
const has = <TObject extends object, TKey extends keyof TObject>(
...keys: TKey[]
) => (object: TObject): object is TObject & HasProperties<TObject, TKey> =>
keys.every(key => isDefined(object[key]))
intersection
intersection: <T>(list: T[]) => (el: T) => boolean
Use with: filter
Returns a list of elements that are present in both lists
[1, 2, 3].filter(intersection([2, 3, 4])); // Returns [2, 3]
const intersection = <T>(list: T[]) => (el: T) =>
findIndex(list, a => a === el) !== -1
intersectionBy
intersectionBy: <T>(func: (el: T) => unknown, list: T[]) => (el: T) => boolean
Use with: filter
Returns a list of elements that are present in both lists compared by running func
on elements in both lists
[{ a: 1 }, { a: 2 }, { a: 3 }]
.filter(intersectionBy(el => el.a, [{ a: 2 }, { a: 3 }, { a: 4 }]));
// Returns [{ a: 2 }, { a: 3 }]
const intersectionBy = <T>(func: (el: T) => unknown, list: T[]) => (
el: T
) => findIndex(list, a => func(a) === func(el)) !== -1
intersectionByProperty
intersectionByProperty: <TObject extends object, TKey extends keyof TObject>(key: TKey, list: TObject[]) => (el: TObject) => boolean
Use with: filter
Returns a list of elements that are present in both lists compared at key
[{ a: 1 }, { a: 2 }, { a: 3 }]
.filter(intersectionByProperty("a", [{ a: 2 }, { a: 3 }, { a: 4 }]));
// Returns [{ a: 2 }, { a: 3 }]
const intersectionByProperty = <
TObject extends object,
TKey extends keyof TObject
>(
key: TKey,
list: TObject[]
) => intersectionBy(get(key), list)
is
is: <T>(value: T) => (el: T) => boolean
Use with: find
, filter
Returns true
for elements that are equal to value
[1,2,3].find(is(1)); // Returns 1
[1,1,2].filter(is(1)); // Returns [1, 1]
const is = <T>(value: T) => (el: T) => el === value
isBy
isBy: <T, U>(func: (el: T) => U, value: U) => (el: T) => boolean
Use with: find
, filter
Returns true
for elements where func(element)
equals value
[{ a: 1 }, { a: 2 }].find(isBy(el => el.a, 2)); // Returns { a: 2 }
[{ a: 1 }, { a: 2 }].filter(isBy(el => el.a, 2)); // Returns [{ a: 2 }]
const isBy = <T, U>(func: (el: T) => U, value: U) => (el: T) =>
func(el) === value
isDefined
isDefined: <T>(x: T) => x is NonNullable<T>
Use with: filter
Remove elements that are undefined
or null
[1, null, undefined, 2].filter(isDefined); // Returns [1, 2]
const isDefined = <T>(x: T): x is NonNullable<T> =>
x !== undefined && x !== null
isOneOf
isOneOf: <T>(list: T[]) => (el: T) => boolean
Use with: find
, filter
Alias for intersection
. Returns true
for elements that exist in the provided list
[1,1,2,2,3].filter(isOneOf([2,3])); // Returns [2, 2, 3]
const isOneOf = intersection
isOneOfBy
isOneOfBy: <T, U>(func: (el: T) => U, list: U[]) => (el: T) => boolean
Use with: find
, filter
Returns true
for elements where func(element)
exists in list
[{ a: 1 }, { a: 2 }, { a: 3 }].find(isOneOfBy(el => el.a, [2, 3]));
// ^ Returns { a: 2 }
[{ a: 1 }, { a: 2 }, { a: 3 }].filter(isOneOfBy(el => el.a, [2, 3]));
// ^ Returns [{ a: 2 }, { a: 3 }]
const isOneOfBy = <T, U>(func: (el: T) => U, list: U[]) => (el: T) =>
findIndex(list, a => a === func(el)) !== -1
isnt
isnt: <T>(value: T) => (el: T) => boolean
Use with: find
, filter
Returns true
for elements that are not equal to value
[1,2,3].find(isnt(1)); // Returns 2
[1,2,2].filter(isnt(1)); // Returns [2,2]
const isnt = <T>(value: T) => (el: T) => el !== value
isntBy
isntBy: <T, U>(func: (el: T) => U, value: U) => (el: T) => boolean
Use with: find
, filter
Returns true
for elements where func(element)
does not equal value
[{ a: 1 }, { a: 2 }].find(isntBy(el => el.a, 2)); // Returns { a: 1 }
[{ a: 1 }, { a: 2 }].filter(isntBy(el => el.a, 2)); // Returns [{ a: 1 }]
const isntBy = <T, U>(func: (el: T) => U, value: U) => (el: T) =>
func(el) !== value
isntOneOf
isntOneOf: <T>(list: T[]) => (el: T) => boolean
Use with: find
, filter
Alias for exclude
. Returns true
for elements that do not exist in the provided list
[1,1,2,2,3].filter(isntOneOf([2,3])); // Returns [1, 1]
const isntOneOf = exclude
isntOneOfBy
isntOneOfBy: <T, U>(func: (el: T) => U, list: U[]) => (el: T) => boolean
Use with: find
, filter
Returns true
for elements where func(element)
exists in list
[{ a: 1 }, { a: 2 }, { a: 3 }].find(isntOneOfBy(el => el.a, [2, 3]));
// ^ Returns { a: 1 }
[{ a: 1 }, { a: 2 }, { a: 3 }].filter(isntOneOfBy(el => el.a, [2, 3]));
// ^ Returns [{ a: 1 }]
const isntOneOfBy = <T, U>(func: (el: T) => U, list: U[]) => (el: T) =>
findIndex(list, a => a === func(el)) === -1
max
max: (acc: number, el: number) => number
Use with: reduce
Returns the largest value in the list
[1,2,3,4].reduce(max); // Returns 4
const max = (acc: number, el: number) => Math.max(acc, el)
maxBy
maxBy: <T>(func: (el: T) => number) => (acc: T, el: T) => T
Use with: reduce
Returns the largest element by comparing func(element)
[{ a: 1 }, { a: 2 }, { a: 3 }].reduce(maxBy(el => el.a)); // Returns { a: 3 }
const maxBy = <T>(func: (el: T) => number) => (acc: T, el: T) =>
func(el) > func(acc) ? el : acc
maxByProperty
maxByProperty: <TObject extends object, TKey extends keyof TObject>(key: TKey) => (acc: TObject, el: TObject) => TObject
Use with: reduce
Returns the largest element by comparing element[key]
[{ a: 1 }, { a: 2 }, { a: 3 }].reduce(maxByProperty("a")); // Returns { a: 3 }
const maxByProperty = <
TObject extends object,
TKey extends keyof TObject
>(
key: TKey
) => (acc: TObject, el: TObject) => (el[key] > acc[key] ? el : acc)
min
min: (acc: number, el: number) => number
Use with: reduce
Returns the smallest value in the list
[1,2,3,4].reduce(min); // Returns 1
const min = (acc: number, el: number) => Math.min(acc, el)
minBy
minBy: <T>(func: (el: T) => number) => (acc: T, el: T) => T
Use with: reduce
Returns the smallest element by comparing func(element)
[{ a: 1 }, { a: 2 }, { a: 3 }].reduce(minBy(el => el.a)); // Returns { a: 1 }
const minBy = <T>(func: (el: T) => number) => (acc: T, el: T) =>
func(el) < func(acc) ? el : acc
minByProperty
minByProperty: <TObject extends object, TKey extends keyof TObject>(key: TKey) => (acc: TObject, el: TObject) => TObject
Use with: reduce
Returns the smallest element by comparing element[key]
[{ a: 1 }, { a: 2 }, { a: 3 }].reduce(minByProperty("a")); // Returns { a: 1 }
const minByProperty = <
TObject extends object,
TKey extends keyof TObject
>(
key: TKey
) => (acc: TObject, el: TObject) => (el[key] < acc[key] ? el : acc)
or
or: <T>(fallback: NonNullable<T>) => (x: T) => NonNullable<T>
Use with: map
Replaces list elements that are undefined
or null
with fallback
[1, null, undefined, 2].map(or(0)); // Returns [1, 0, 0, 2]
const or = <T>(fallback: NonNullable<T>) => (x: T): NonNullable<T> =>
isDefined(x) ? x : fallback
partition
partition: <T>(func: (el: T) => boolean) => (acc: T[][], el: T) => T[][]
Use with: reduce
Splits the input list into two lists. The first list contains elements for which the given function returned true
, the second contains elements for which the function returned false
.
[{ age: 10 }, { age: 80 }].reduce(partition(el => el.age > 30), []);
// Returns [[{ age: 80 }], [{ age: 10 }]]
const partition = <T>(func: (el: T) => boolean) => (
acc: T[][],
el: T
) => {
const a0 = acc[0] || [],
a1 = acc[1] || [];
if (func(el)) a0.push(el);
else a1.push(el);
return [a0, a1];
}
propertyIs
propertyIs: <TObject extends object, TKey extends keyof TObject>(key: TKey, value: TObject[TKey]) => (el: TObject) => boolean
Use with: find
, filter
Returns true
for elements where element[key]
equals value
[{ a: 1 }, { a: 2 }].find(propertyIs("a", 2)); // Returns { a: 2 }
[{ a: 1 }, { a: 2 }].filter(propertyIs("a", 2)) // Returns [{ a: 2 }]
const propertyIs = <TObject extends object, TKey extends keyof TObject>(
key: TKey,
value: TObject[TKey]
) => isBy(get(key), value)
propertyIsOneOf
propertyIsOneOf: <TObject extends object, TKey extends keyof TObject>(key: TKey, list: TObject[TKey][]) => (el: TObject) => boolean
Use with: find
, filter
Returns true
for elements where element[key]
exists in list
[{ a: 1 }, { a: 2 }, { a: 3 }].find(propertyIsOneOf("a", [2, 3]));
// ^ Returns { a: 2 }
[{ a: 1 }, { a: 2 }, { a: 3 }].filter(propertyIsOneOf("a", [2, 3]));
// ^ Returns [{ a: 2 }, { a: 3 }]
const propertyIsOneOf = <
TObject extends object,
TKey extends keyof TObject
>(
key: TKey,
list: TObject[TKey][]
) => isOneOfBy(get(key), list)
propertyIsnt
propertyIsnt: <TObject extends object, TKey extends keyof TObject>(key: TKey, value: TObject[TKey]) => (el: TObject) => boolean
Use with: find
, filter
Returns true
for elements where element[key]
does not equal value
[{ a: 1 }, { a: 2 }].find(propertyIsnt("a", 2)); // Returns { a: 1 }
[{ a: 1 }, { a: 2 }].filter(propertyIsnt("a", 2)); // Returns [{ a: 1 }]
const propertyIsnt = <
TObject extends object,
TKey extends keyof TObject
>(
key: TKey,
value: TObject[TKey]
) => isntBy(get(key), value)
propertyIsntOneOf
propertyIsntOneOf: <TObject extends object, TKey extends keyof TObject>(key: TKey, list: TObject[TKey][]) => (el: TObject) => boolean
Use with: find
, filter
Returns true
for elements where element[key]
exists in list
[{ a: 1 }, { a: 2 }, { a: 3 }].find(propertyIsntOneOf("a", [2, 3]));
// ^ Returns { a: 1 }
[{ a: 1 }, { a: 2 }, { a: 3 }].filter(propertyIsntOneOf("a", [2, 3]));
// ^ Returns [{ a: 1 }]
const propertyIsntOneOf = <
TObject extends object,
TKey extends keyof TObject
>(
key: TKey,
list: TObject[TKey][]
) => isntOneOfBy(get(key), list)
sum
sum: (acc: number, element: number) => number
Use with: reduce
Sum a list of numbers
[1, 2, 3].reduce(sum); // Returns 6
const sum = (acc: number, element: number) => acc + element
sumBy
sumBy: {
<T>(func: (el: T) => number): (acc: number, el: T) => number;
<T>(func: (el: number) => number): (acc: number, el: number) => number;
}
Use with: reduce
Sums the values by applying func
to elements. If the list elements aren't numbers, a number must be passed as the second argument to reduce
.
[{ a: 1 }, { a: 2 }].reduce(sumBy(el => el.a), 0); // Returns 3
[1.5, 2.5].reduce(sumBy(Math.floor)); // Returns 3
export function sumBy<T>(func: (el: T | number) => number) {
return (acc: number, el: T | number) =>
typeof el === "number" ? func(acc) + func(el) : acc + func(el);
}
sumByProperty
sumByProperty: <TObject extends { [key: string]: number; }, TKey extends keyof TObject>(key: TKey) => (acc: number, el: TObject) => number
Use with: reduce
Sums the values of element[key]
for all elements. A number must be passed to the second argument of reduce
.
[{ a: 1 }, { a: 2 }].reduce(sumByProperty('a'), 0); // Returns 3
const sumByProperty = <
TObject extends { [key: string]: number },
TKey extends keyof TObject
>(
key: TKey
) => (acc: number, el: TObject) => acc + el[key]
unique
unique: (el: unknown, index: number, list: unknown[]) => boolean
Use with: filter
Removes duplicates from list
[1,1,1,2].filter(unique); // Returns [1, 2]
const unique = uniqueBy(el => el)
uniqueBy
uniqueBy: <T>(func: (el: T) => unknown) => (el: T, index: number, list: T[]) => boolean
Use with: filter
Removes duplicates compared by func(element)
[{ a: 1 }, { a : 1 }].filter(uniqueBy(el => el.a)); // Returns [{ a: 1 }]
const uniqueBy = <T>(func: (el: T) => unknown) => (
el: T,
index: number,
list: T[]
) => index === findIndex(list, t => func(t) === func(el))
uniqueByProperty
uniqueByProperty: <TObject extends object, TKey extends keyof TObject>(key: TKey) => (el: TObject, index: number, list: TObject[]) => boolean
Use with: filter
Removes duplicates compared by element[key]
[{ a: 1 }, { a: 1 }].filter(uniqueByProperty('a')); // Return [{ a: 1 }]
const uniqueByProperty = <
TObject extends object,
TKey extends keyof TObject
>(
key: TKey
) => uniqueBy<TObject>(get(key))