Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 12 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
A utility library designed to simplify working with deeply nested objects in TypeScript. This is intended to be used when you work with dynamic keys or when you are not sure if the property exists. It provides a set of functions so you don't have to deal with the complexity of checking for the existence of properties at each level of the object.
A utility library designed to simplify working with deeply nested objects in TypeScript.
This is intended to be used when you work with dynamic keys or when you are not sure if the property exists.
It provides a set of functions so you don't have to deal with the complexity of checking for the existence of properties at each level of the object.

## Features

Expand All @@ -17,9 +19,11 @@ npm install @nuc-lib/deep-key

### Accessing Nested Properties

The `getKeyValue` utility lets you access nested properties using a dot notation string. If the property does not exist, it returns `undefined` instead of throwing an error.
The `getKeyValue` utility lets you access nested properties using a dot notation string.
If the property does not exist, it returns `undefined` instead of throwing an error.

The key is a string that represents the path to the property you want to access. The path is defined using dot notation, where each level of the object is separated by a dot.
The key is a string that represents the path to the property you want to access.
The path is defined using dot notation, where each level of the object is separated by a dot.

```javascript
import { getKeyValue } from '@nuc-lib/deep-key';
Expand All @@ -43,16 +47,14 @@ const guy = {
getKeyValue({ object: guy, key: 'personalInfo.name' }); // 'John Doe'
getKeyValue({ object: guy, key: 'personalInfo.age' }); // 12
getKeyValue({ object: guy, key: 'personalInfo.city' }); // 'New York'
getKeyValue({ object: guy, key: 'personalInfo.active' }); // undefined -> no error thrown
```

For arrays there are two ways to access the values:

- **Getting an element in a specific index.**

```javascript
getKeyValue({ object: guy, key: 'contacts.0' });
// { name: 'Jane Doe', email: 'afk@example.com' }
getKeyValue({ object: guy, key: 'contacts.0' }); // { name: 'Jane Doe', email: 'afk@example.com' }
getKeyValue({ object: guy, key: 'contacts.0.name' }); // 'Jane Doe'
getKeyValue({ object: guy, key: 'contacts.0.email' }); // 'afk@example.com'
```
Expand All @@ -69,7 +71,8 @@ getKeyValue({ object: guy, key: '[contacts].invalidKey' });

### Filtering By Nested Properties

The `filterByKeyValue` utility allows you to filter an array of objects based on a nested property. It returns a new array containing only the objects that match the specified key and value.
The `filterByKeyValue` utility allows you to filter an array of objects based on a nested property.
It returns a new array containing only the objects that match the specified key and value.

```javascript
import { filterByKeyValue } from '@nuc-lib/deep-key';
Expand Down Expand Up @@ -111,7 +114,6 @@ filterByKeyValue({
filter: (value) => value === 25
});
// [ { id: 1, name: 'John Doe', age: 25, parentIds: [ 1, 2 ], address: { city: 'Houston', zip: '10001' } } ]

filterByKeyValue({
array: people,
key: 'address.city',
Expand All @@ -121,12 +123,6 @@ filterByKeyValue({
// { id: 1, name: 'John Doe', age: 25, parentIds: [ 1, 2 ], address: { city: 'Houston', zip: '10001' } },
// { id: 4, name: 'Bob Johnson', age: 28, parentIds: [7, 8], address: { city: 'Houston', zip: '77001' } }
// ]
filterByKeyValue({
array: people,
key: 'address',
filter: (value) => value === 'Houston'
});
// [] -> no error thrown
filterByKeyValue({
array: people,
key: 'age',
Expand All @@ -149,7 +145,8 @@ filterByKeyValue({

### Sorting By Nested Properties

The `sortByKeyValue` utility allows you to sort an array of objects based on a nested property. It returns a new array sorted in ascending order by default.
The `sortByKeyValue` utility allows you to sort an array of objects based on a nested property.
It returns a new array sorted in ascending order by default.

```javascript
import { sortByKeyValue } from '@nuc-lib/deep-key';
Expand Down
49 changes: 49 additions & 0 deletions lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,52 @@ export type DeepKeyOf<T extends TObject> = keyof {
: `${K}.${DeepKeyOf<T[K]>}`
: never]: unknown;
};

/**
* Represents the type of a value at a specific key in an object, including nested keys.
* This type is useful for creating dynamic forms or validating object structures.
*/
export type DeepTypeOfKey<T extends TObject, K extends string> =
// array.0.x
K extends `${infer Key}.${number}.${infer Rest}`
? Key extends keyof T
? T[Key] extends Array<infer U>
? U extends TObject
? DeepTypeOfKey<U, Rest>
: never
: never
: never
: // array.0
K extends `${infer Key}.${number}`
? Key extends keyof T
? T[Key] extends Array<infer U>
? U
: never
: never
: // [array].x
K extends `[${infer Key}].${infer Rest}`
? Key extends keyof T
? T[Key] extends Array<infer U>
? U extends TObject
? DeepTypeOfKey<U, Rest>
: never
: never
: never
: // [array]
K extends `[${infer Key}]`
? Key extends keyof T
? T[Key] extends Array<infer U>
? U
: never
: never
: // object.key
K extends `${infer Key}.${infer Rest}`
? Key extends keyof T
? T[Key] extends TObject
? DeepTypeOfKey<T[Key], Rest>
: never
: never
: // direct key
K extends keyof T
? T[K]
: never;
17 changes: 6 additions & 11 deletions lib/utils/filter.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { getKeyValue } from './value';

import type { DeepKeyOf, TObject } from '../types';
import type { DeepKeyOf, DeepTypeOfKey, TObject } from '../types';

type FilterParams<T extends TObject> = {
type FilterParams<T extends TObject, K extends DeepKeyOf<T>> = {
array: T[];
key: DeepKeyOf<T>;
filter: (value: unknown) => boolean;
key: K;
filter: (value: DeepTypeOfKey<T, K> | undefined) => boolean;
};

/**
Expand All @@ -14,18 +14,13 @@ type FilterParams<T extends TObject> = {
* @param params - The parameters for filtering, including the array of objects, the key to filter by, and the filter function.
* @returns A new array with the filtered objects.
*/
export const filterByKeyValue = <T extends TObject>({
export const filterByKeyValue = <T extends TObject, K extends DeepKeyOf<T>>({
array,
filter,
key
}: FilterParams<T>) => {
}: FilterParams<T, K>): T[] => {
return array.filter((item) => {
const itemKeyValue = getKeyValue({ object: item, key });

// Early return if itemKeyValue is undefined
if (!itemKeyValue) {
return false;
}
return filter(itemKeyValue);
});
};
2 changes: 1 addition & 1 deletion lib/utils/sort.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const sortByKeyValue = <T extends TObject>({
array,
key,
order = 'ASC'
}: SortParams<T>) => {
}: SortParams<T>): T[] => {
const sortedArray = array.toSorted((a, b) => {
const aValue = getKeyValue({ object: a, key });
const bValue = getKeyValue({ object: b, key });
Expand Down
14 changes: 7 additions & 7 deletions lib/utils/value.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { KeyOf, TObject } from '../types';
import type { DeepTypeOfKey, KeyOf, TObject } from '../types';

type Params<T extends TObject> = {
type Params<T extends TObject, K extends KeyOf<T>> = {
object: T;
key: KeyOf<T>;
key: K;
};

/**
* Gets the value of a key in an object, including nested keys.
* @param params - The parameters for getting the key value, including the object and the key
* The key is represented as a string, with nested keys separated by dots (e.g. 'key1.key2.key3').
* The key is represented as a string, with nested keys separated by dots (e.g. `key1.key2.key3`).
* @returns The value of the key in the object.
*/
export const getKeyValue = <T extends TObject>({
export const getKeyValue = <T extends TObject, K extends KeyOf<T>>({
object,
key
}: Params<T>): unknown => {
}: Params<T, K>): DeepTypeOfKey<T, K> | undefined => {
const subKeys = String(key)
.split('.')
.map((key) => key.replace(/[[\]]/g, ''));
// Initialize the value with the object
let value: any = object;

// If the object is an array, return undefined
// This is to prevent the function from returning an array empty objects
// This is to prevent the function from returning an array of empty objects
if (Array.isArray(object)) {
return undefined;
}
Expand Down
Loading
Loading