Skip to content

The loosely typed omit overload makes the strictly typed one virtually useless #1569

@deivshon

Description

@deivshon

Since 3a996de omit has an overload that accepts PropertyKey[] for the keys parameter.

This loosely typed overload makes the strictly typed one practically useless for a few reasons (note that here I will assume the consumer is not specifying the overload explicitly using angle brackets, which is virtually always the case):

  • When using the function with more than one key in the array (weirdly, it does not happen with the first key), TypeScript's LSP does not seem to be able to suggest the proper keys, as typing the empty string ("") makes it assume you're going for the loosely typed overload.
  • When using the strictly typed overload, if you delete some field that you were omit-ting from your type, no compile time error will be triggered, as that omit call will simply switch to the loosely typed overload. Though this does not cause runtime errors, it does increase unnecessary code and uncertainty in future refactors.

Fundamentally, having both omit overloads gives a false sense of security when using it and drastically degrades developer experience.

Moreover, these significant cons come with a single pro: allowing key arrays whose types are too wide to fit the strictly typed omit overload to be passed to the loosely typed omit overload. These situations seldom occur, and when they do such as in #1490, the consumer could simply cast the keys or parse them before passing them, taking on the responsibility of verifying their type.

Possible solutions

  1. Remove the loosely typed overload (which, as you will have guessed, I favor)
  2. Remove the strictly typed overload
  3. Separate the two overloads into two different functions (strictOmit, looseOmit) and remove the current omit

Since the loosely typed overload is already released (and has been for quite some time), omit will at least need to preserve its current type behavior until the next major is released. Given this constraint, 1 and 2 are only applicable in the next major, though if you end up choosing 1 you could add the @deprecated JSDoc tag to the loosely typed overload so users know not to use it. 3 could be applicable today, and the @deprecated JSDoc tag could be applied to the current omit waiting to be removed in the next major.

Workaround

Lastly, I would like to share my current workaround for anyone that might stumble upon this and need it.

I created these two functions:

import { omit } from "es-toolkit"

export const strictOmit = <T extends Record<PropertyKey, unknown>, K extends keyof T>(
    obj: T,
    keys: readonly K[],
): Omit<T, K> => omit<T, K>(obj, keys)
export const looseOmit = <T extends Record<PropertyKey, unknown>>(
    obj: T,
    keys: readonly PropertyKey[],
): Partial<T> => omit<T>(obj, keys)

and enforced their use using the @typescript/no-restricted-imports rule with eslint and @typescript/eslint:

[...]
"no-restricted-imports": "off",
"@typescript-eslint/no-restricted-imports": [
    "error",
    {
        paths: [
            {
                name: "es-toolkit",
                importNames: ["omit"],
                message: "Use `strictOmit` or `looseOmit` instead",
            },
        ],
    },
],
[...]

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions