0

Take this code as an example:

function isKeyOf<T extends {}>(
  key: string, // this: string,
  obj: T,
): key is keyof T {
  return key in obj;
}

This works fine, but what if I want to do chaining (like an extension method)

In usage like this:

let key: string;
// ...
if (key.isKeyOf(obj)) {
  obj[key] = value;
  // ...
}

I have to do something like

// declare global {
interface /*Custom*/String {
  isKeyOf<T extends object>(obj: T): this is { value: keyof T }
}
// }

But if I try:

CustomString.prototype.isKeyOf = function<T extends {}>(obj: T): this is keyof T {
  return this in obj;
}

TS compiler won't let me use this in type guard functions.

How to implement type narrowing in case that str in obj infers str is of type keyof obj ?

3
  • It's widely considered bad practice to extend native prototypes unless you're polyfilling. Since I presume you don't want to talk about that, could you edit the question to remove that aspect of it? Make up your own interface/class and extend its prototype and we can then work on how to get this-like behavior, if you want.
    – jcalz
    Commented Apr 28, 2023 at 2:44
  • Please ask a single question per post; the question at the end should probably be removed.
    – jcalz
    Commented Apr 28, 2023 at 2:49
  • 1
    Assuming you edit like I've asked in my previous comments, then this approach is how I'd do it. You just need to merge in the method signature and then the implementation can be wider (and return a boolean). Does that fully address the question? If so I'll write up an answer explaining. If not, what am I missing?
    – jcalz
    Commented Apr 28, 2023 at 2:55

1 Answer 1

0

Thanks to @jcalz example, which solves the original question:

You don't need to specify type guard at implementation, just return boolean as normally what it does.

For a better approach committed by @jcalz, please see: this approach

Here's a personal demo.
NOTE: Extending native prototypes can be a bad practice, do not use unless you know the disadvantages

// declaration & implementation
// # extension.ts
declare global {
  interface String {
    isKeyOf<T extends {}>(obj: T): this is keyof T
  }
}

// just leave type guard blank in implementation.
String.prototype.isKeyOf = function <T extends {}>(this: string, obj: T) {
  return Object.keys(obj).includes(this)
}

export {}

// # foo.ts (import this before any usage)
// import './extension'

const obj = { a: 1, b: 2, c: 3 }

let key = 'a'
if (key in obj) {
  obj[key] = 4
  // expression of type 'string' can't be used to index type '{ a: number; b: number; c: number; }'
}

if (key.isKeyOf(obj)) {
  obj[key] = 4
}

Not the answer you're looking for? Browse other questions tagged or ask your own question.