Typescript property type guards on unknown
Asked Answered
G

2

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();
};
Globin answered 25/10, 2021 at 9:17 Comment(10)
try to cast obj to any before checking for fooSeethe
@RickyMo you mean (obj: any) => {... ?Globin
Is there a specific reason you're using unknown? any would work, but you've specifically mentioned unknown so...Aulea
https://mcmap.net/q/53939/-39-unknown-39-vs-39-any-39Seethe
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 userGlobin
I tried with the cast @RickyMo it still shows an error typescriptlang.org/play?#code/…Globin
any works but it's not what i wantGlobin
So what do you want?Seethe
By const bar = obj as any, you can access properties of obj. You need it to be any to access its properties. You then checked 'foo' in bar && typeof bar.foo === 'string', which fullfill your requirement. What else do you want?Seethe
In this case unknown doesn't add any additional type safety. You could just: const foo = (obj: any) => obj?.foo === 'string' ? obj?.foo : undefinedHighmuckamuck
U
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;
        }
    }
};
Uncertainty answered 25/10, 2021 at 9:28 Comment(0)
F
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

Franciscka answered 25/10, 2021 at 9:28 Comment(0)
U
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;
        }
    }
};
Uncertainty answered 25/10, 2021 at 9:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.