Skip to main content

prefer-ts-extras-key-in

Prefer keyIn from ts-extras over key in object checks.

keyIn(...) provides better key narrowing for dynamic property checks.

Targeted pattern scopeโ€‹

This rule focuses on direct key in object expressions that can be migrated to keyIn(key, object) with deterministic fixes.

  • Native key in object expressions that can use keyIn(key, object).

Alias indirection, wrapper helpers, and non-canonical call shapes are excluded to keep keyIn(key, object) migrations safe.

What this rule reportsโ€‹

This rule reports key in object expressions when keyIn(key, object) is the intended replacement.

  • Native key in object expressions that can use keyIn(key, object).

Why this rule existsโ€‹

keyIn expresses key-membership checks with a helper that improves key narrowing in typed code.

  • Dynamic key checks have one canonical form.
  • Guarded property access needs fewer casts.
  • Membership guards are easier to audit in review.

โŒ Incorrectโ€‹

if (key in payload) {
// ...
}

โœ… Correctโ€‹

if (keyIn(key, payload)) {
// ...
}

Behavior and migration notesโ€‹

  • Runtime semantics match the in operator for property existence on object/prototype chains.
  • keyIn is useful when key values start as string/PropertyKey and need narrowing.
  • If your code intentionally requires direct in syntax (for style or tooling), keep native checks.

Additional examplesโ€‹

โŒ Incorrect โ€” Additional exampleโ€‹

if (candidate in record) {
console.log(record[candidate as keyof typeof record]);
}

โœ… Correct โ€” Additional exampleโ€‹

if (keyIn(candidate, record)) {
console.log(record[candidate]);
}

โœ… Correct โ€” Repository-wide usageโ€‹

const canRead = keyIn(data, selectedKey);

ESLint flat config exampleโ€‹

import typefest from "eslint-plugin-typefest";

export default [
{
plugins: { typefest },
rules: {
"typefest/prefer-ts-extras-key-in": "error",
},
},
];

When not to use itโ€‹

Disable this rule if direct in expressions are required by coding standards.

Package documentationโ€‹

ts-extras package documentation:

Source file: source/key-in.ts

/**
* Check if a key exists in an object and narrow the key type.
*
* This function performs **key narrowing** - it narrows the key variable to
* only keys that actually exist in the object. Uses the `in` operator to check
* the entire prototype chain.
*
* When `keyIn` returns `true`, the key is narrowed to keys that exist in the
* object. When it returns `false`, the key type remains unchanged.
*
* Unlike `objectHasIn` and `objectHasOwn` (both do object narrowing), this
* narrows the _key_ type, making it useful for validating union types of
* possible keys.
*
* @category Type guard
*
* @example
* ```
* import {keyIn} from 'ts-extras';
*
* const object = {foo: 1, bar: 2};
* const key = 'foo' as 'foo' | 'bar' | 'baz';
*
* if (keyIn(object, key)) {
* // `key` is now: 'foo' | 'bar' (narrowed from union)
* console.log(object[key]); // Safe access
* } else {
* // `key` remains: 'foo' | 'bar' | 'baz' (unchanged)
* }
*
* // Works with symbols
* const symbol = Symbol.for('myKey');
* const objectWithSymbol = {[symbol]: 'value'};
* if (keyIn(objectWithSymbol, symbol)) {
* // symbol is narrowed to existing symbol keys
* }
* ```
*
* @note This uses the `in` operator and checks the prototype chain, but blocks `__proto__` and `constructor` for security.
*/

Rule catalog ID: R024

Further readingโ€‹

Adoption resourcesโ€‹