Skip to content

Commit

Permalink
fix: conditional object return type
Browse files Browse the repository at this point in the history
  • Loading branch information
gao-sun committed Aug 5, 2023
1 parent af86356 commit 3a27cf4
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 7 deletions.
24 changes: 24 additions & 0 deletions src/utilities/conditional.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { condObject, conditionalObject } from './conditional.js';

describe('conditionalObject', () => {
it('should return a new object with all falsy values removed', () => {
const result = conditionalObject({ foo: 'foo', bar: undefined, baz: false } as const);
const result2 = condObject({ foo: 'foo', bar: undefined, baz: false });

// @ts-expect-error - `baz` is removed from the object.
type _ = typeof result['baz'];

// Should be `true | undefined` because `baz` could be true in the original type `boolean`.
type __ = typeof result2['baz'];

expect(result).toEqual({ foo: 'foo' });
expect(result2).toEqual({ foo: 'foo' });
});

it('should not remove falsy values nested in objects', () => {
expect(conditionalObject({ foo: 'foo', bar: { baz: false } })).toEqual({
foo: 'foo',
bar: { baz: false },
});
});
});
25 changes: 18 additions & 7 deletions src/utilities/conditional.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,20 @@ export const conditionalArray = <T>(
/** Alias for {@link conditionalArray}. */
export const condArray = conditionalArray;

/**
* An object with all {@link Falsy} values removed while keeping the object structure.
* Note `undefined` definitions are not removed from the object in some conditions
* because it's uncertain whether the value is falsy or not.
*/
export type TruthyObject<T extends Record<string, unknown>> = {
// Directly remove the key if the type is falsy.
[K in keyof T as T[K] extends Falsy ? never : K]: [T[K] & Falsy] extends [never]
? // No intersection with falsy types, keep the type.
T[K]
: // If the type could be falsy, make it optional.
Optional<Truthy<T[K]>>;
};

/**
* Return a new object with all {@link Falsy} values removed.
* This function only performs a shallow removal, i.e. it does not remove
Expand All @@ -64,14 +78,11 @@ export const condArray = conditionalArray;
* }) // { foo: 'foo' }
* ```
*/
export const conditionalObject = <T extends Record<string, unknown>>(
object: T
): Record<keyof T, Truthy<T[keyof T]>> =>
export const conditionalObject = <T extends Record<string, unknown>>(object: T): TruthyObject<T> =>
// eslint-disable-next-line no-restricted-syntax
Object.fromEntries(Object.entries(object).filter(([, value]) => notFalsy(value))) as Record<
keyof T,
Truthy<T[keyof T]>
>;
Object.fromEntries(
Object.entries(object).filter(([, value]) => notFalsy(value))
) as TruthyObject<T>;

/** Alias for {@link conditionalObject}. */
export const condObject = conditionalObject;

0 comments on commit 3a27cf4

Please sign in to comment.