Reputation: 55
I'm new to functional programming but wanna learn best practices.
What is the proper fp-ts way to convert array into object?
(items: Item[], keyGetter: (i: Item) => Key) => Record<Key, Item>
I use my own not fp-ts implementation so far:
function isDefined<T>(value: T): value is Exclude<T, undefined> {
return value !== undefined;
}
type TIdentifier = string | number;
export const arrayToRecord = <T1, T2 extends TIdentifier = string>(
arr: T1[],
getKeyName?: (item: T1) => T2
): Record<T2, T1> => {
const hasKeyNameGetter = isDefined(getKeyName);
return arr.reduce((acc, item) => {
acc[
hasKeyNameGetter ? (getKeyName as (item: T1) => T2)(item) : ((item as unknown) as T2)
] = item;
return acc;
}, {} as Record<T2, T1>);
};
Upvotes: 0
Views: 2641
Reputation: 41
Here's a couple of solutions that build on Dmitrii's idea to use fromFoldableMap
and make it generic as in Denis' answer:
const arrayToRecord =
<T>(keyGetter: (i: T) => string) =>
(items: ReadonlyArray<T>): Readonly<Record<string, T>> =>
R.fromFoldableMap(last<T>(), A.Foldable)(items, (item) => [keyGetter(item), item]);
// functionally equivalent, maybe more readable
const arrayToRecord2 = <T>(keyGetter: (i: T) => string) =>
flow(
A.map<T, readonly [string, T]>((item) => [keyGetter(item), item]),
R.fromFoldable(last<T>(), A.Foldable)
);
Here's an example (yes, it is exactly the same as Denis' example):
const xs = [
{ id: "abc", date: new Date() },
{ id: "snt", date: new Date() },
];
const res = pipe(xs, arrayToRecord((x) => x.id));
console.log(res);
// {
// abc: { id: 'abc', date: 2021-04-06T13:09:25.732Z },
// snt: { id: 'snt', date: 2021-04-06T13:09:25.732Z }
// }
const res2 = pipe(xs, arrayToRecord2((x) => x.id));
console.log(res2);
// {
// abc: { id: 'abc', date: 2021-04-06T13:09:25.732Z },
// snt: { id: 'snt', date: 2021-04-06T13:09:25.732Z }
// }
Upvotes: 0
Reputation: 55
This is the only fp-ts
solution:
import * as A from "fp-ts/lib/Array";
import * as R from "fp-ts/lib/Record";
import * as semigroup from 'fp-ts/Semigroup';
const arr = A.fromArray([1,2,3]);
const testRecord = R.fromFoldableMap(
semigroup.last<number>(),
A.array
)(arr, key => [String(key), key]);
Upvotes: 4
Reputation: 968
Here's a way to achieve what you're asking.
Some notes:
Record<string, A>
keyGetter
can't be optional, we must provide a way to came up with e keyimport * as A from 'fp-ts/ReadonlyArray'
import * as R from 'fp-ts/ReadonlyRecord'
import { pipe } from 'fp-ts/function'
const arrayToRecord = <A>(
items: ReadonlyArray<A>,
keyGetter: (i: A) => string,
): Readonly<Record<string, A>> =>
pipe(
items,
A.reduce({}, (acc, item) => pipe(acc, R.upsertAt(keyGetter(item), item))),
)
EDIT
An example as requested:
const xs = [
{ id: 'abc', date: new Date() },
{ id: 'snt', date: new Date() },
]
const res = arrayToRecord(xs, (x) => x.id)
console.log(res)
// {
// abc: { id: 'abc', date: 2021-04-06T13:09:25.732Z },
// snt: { id: 'snt', date: 2021-04-06T13:09:25.732Z }
// }
EDIT 2
pipe
friendly version:
declare const arrayToRecord: <A>(
keyGetter: (i: A) => string,
) => (items: ReadonlyArray<A>) => Readonly<Record<string, A>>
interface X { id: string; date: Date }
declare const xs: ReadonlyArray<X>
pipe(
xs,
arrayToRecord((x) => x.id),
) // Readonly<Record<string, X>>
Upvotes: 1