Skip to main content

Normalize an array of object (or collection) by a given key

The keyBy and collectionKeyBy functions can be useful in several scenarios where you need to transform a collection of objects into an object for easier access (With time complexity: O(1)) or manipulation.

  1. Data Normalization: When you're working with a large dataset, it's often more efficient to store the data in a normalized form. For example, if you have an array of users, you might want to transform it into an object where the keys are user IDs and the values are the user objects. This allows you to quickly and easily look up a user by their ID.

  2. Data Indexing: Similarly, if you're working with a collection of objects that have a unique identifier (like a database ID), you can use these functions to create an index of the objects by their identifier. This can significantly speed up operations like searching for an object by its identifier.

  3. Data Aggregation: If you need to aggregate data based on a certain property, these functions can help. For example, if you have an array of transactions and you want to group them by user ID, you can use keyBy to create an object where the keys are user IDs and the values are arrays of transactions for each user.

/**
* Transforms an array of objects into an object where the keys are the values of a specified key in the objects,
* and the values are the objects themselves. If the array is empty, returns undefined.
*
* @template Obj - The type of the objects in the array. Must extend Record<string, unknown>.
* @template Key - The type of the key to use. Must be a key of T.
* @param {Obj[]} array - The array of objects to transform.
* @param {Key} key - The key to use for the new object.
* @returns {undefined | Record<string, Obj>} - The transformed object, or undefined if the array is empty.
*
* @example
* const array = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }, { id: 3, name: 'Charlie' }];
* const key = 'id';
* const keyedObject = keyBy(array, key);
* console.log(keyedObject); // Outputs: { '1': { id: 1, name: 'Alice' }, '2': { id: 2, name: 'Bob' }, '3': { id: 3, name: 'Charlie' } }
*
* @example
* const emptyArray = [];
* const key = 'id';
* const keyedObject = keyBy(emptyArray, key);
* console.log(keyedObject); // Outputs: undefined
*/
export const keyBy = <
Obj extends Record<string, unknown>,
Key extends keyof Obj,
>(
array: Obj[],
key: Key,
): undefined | Record<string, Obj> =>
array.length === 0
? undefined
: (Object.fromEntries(
array.map(value => [String(key ? value[key] : value), value]),
) as Record<string, Obj>)

/**
* Transforms a collection (either an array or an object) of objects into an object where the keys are the values of a specified key in the objects,
* and the values are the objects themselves. If the collection is empty, returns undefined.
*
* @template Obj - The type of the objects in the collection. Must extend Record<string, unknown>.
* @template Key - The type of the key to use. Must be a key of T.
* @param {Obj[] | Record<string, Obj>} collection - The collection of objects to transform.
* @param {Key} key - The key to use for the new object.
* @returns {undefined | Record<string, Obj>} - The transformed object, or undefined if the collection is empty.
*
* @example
* const array = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }, { id: 3, name: 'Charlie' }];
* const key = 'id';
* const keyedObject = collectionKeyBy(array, key);
* console.log(keyedObject); // Outputs: { '1': { id: 1, name: 'Alice' }, '2': { id: 2, name: 'Bob' }, '3': { id: 3, name: 'Charlie' } }
*
* @example
* const object = { a: { id: 1, name: 'Alice' }, b: { id: 2, name: 'Bob' }, c: { id: 3, name: 'Charlie' } };
* const key = 'id';
* const keyedObject = collectionKeyBy(object, key);
* console.log(keyedObject); // Outputs: { '1': { id: 1, name: 'Alice' }, '2': { id: 2, name: 'Bob' }, '3': { id: 3, name: 'Charlie' } }
*
* @example
* const emptyArray = [];
* const key = 'id';
* const keyedObject = collectionKeyBy(emptyArray, key);
* console.log(keyedObject); // Outputs: undefined
*/
export const collectionKeyBy = <
Obj extends Record<string, unknown>,
Key extends keyof Obj,
>(
collection: Obj[] | Record<string, Obj>,
key: Key,
) =>
Array.isArray(collection)
? keyBy(collection, key)
: keyBy(Object.values(collection), key)

/**
* @description Alias for the {@link collectionKeyBy} function.
*/
export const normalizeBy = collectionKeyBy