6

I'm trying to type guard an unknown type

const foo  = (obj: unknown) => {
    if (typeof obj === 'object' && obj) {
        if ('foo' in obj && typeof obj.foo === 'string') {
            return obj.foo;
        }
    }
};

But I'm getting

Property 'foo' does not exist on type 'object'.

I also tried with is expression does not work:

const foo  = (obj: unknown): obj is { foo: 'string' } => {
    if (typeof obj === 'object' && obj) {
        if ('foo' in obj && typeof obj.foo === 'string') {
            return obj;
        }
    }
    throw new Error();
};
10
  • try to cast obj to any before checking for foo Commented Oct 25, 2021 at 9:19
  • @RickyMo you mean (obj: any) => {... ? Commented Oct 25, 2021 at 9:19
  • 1
    Is there a specific reason you're using unknown? any would work, but you've specifically mentioned unknown so... Commented Oct 25, 2021 at 9:21
  • https://stackoverflow.com/questions/51439843/unknown-vs-any Commented Oct 25, 2021 at 9:22
  • I'm using gts preset, which throws me a warning when using any. And I prefer to type check the code because it's something written by the user Commented Oct 25, 2021 at 9:24

2 Answers 2

3

You're going to have to give TypeScript a little help here:

type fooObj = object & { foo: unknown };
const foo = (obj: unknown) => {
    if (typeof obj === 'object' && obj) {
        if ('foo' in obj && typeof (obj as fooObj).foo === 'string') {
            return (obj as fooObj).foo;
        }
    }
};
Sign up to request clarification or add additional context in comments.

Comments

3

Please consider using this helper:

const hasProperty = <Obj, Prop extends string>(obj: Obj, prop: Prop)
  : obj is Obj & Record<Prop, unknown> =>
  Object.prototype.hasOwnProperty.call(obj, prop);

in your case. in operator works as expected mostly with unions. Please see here, here and here

Working solution:

const hasProperty = <Obj, Prop extends string>(obj: Obj, prop: Prop)
  : obj is Obj & Record<Prop, unknown> =>
  Object.prototype.hasOwnProperty.call(obj, prop);

const foo = (obj: unknown) => {
  if (typeof obj === 'object' && obj) {
    if (hasProperty(obj, 'foo') && typeof obj.foo === 'string') {
      return obj.foo;
    }
  }
};

Playground

However, since you want to throw an error if obj is invalid, you can use assert function:

const hasProperty = <Obj, Prop extends string>(obj: Obj, prop: Prop)
  : obj is Obj & Record<Prop, unknown> =>
  Object.prototype.hasOwnProperty.call(obj, prop);

function foo(obj: unknown): asserts obj is { foo: string } {
  const isValid =
    typeof obj === 'object' &&
    obj &&
    hasProperty(obj, 'foo') &&
    typeof obj.foo === 'string';

  if (!isValid) {
    throw new Error();
  }

};

declare var obj: unknown;

foo(obj);

obj.foo // ok

Playground

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.