Typescript: optional chaining does not work for unknown

Created on 20 Dec 2019  路  13Comments  路  Source: microsoft/TypeScript

Search Terms:
optional chaining unknown

Code
In a real world scenario I have an api which returns unknown

const prop: unknown = { key : { value: "Hello World" } };

const helloworld = prop?.key?.value;

Expected behavior:
I want to assign prop?.key?.value to helloworld, since it exists;

Actual behavior:
I get the following error for prop:
const prop: unknown
Object is of type 'unknown'.(2571)

Playground Link:
https://www.typescriptlang.org/play/?ssl=2&ssc=22&pln=1&pc=1#code/MYewdgzgLgBADgJxHAXDArmA1mEB3MGAXhgG8YsBTATxjXIDcBDAG3UrQCIAJSllkDADqIBCwAmnGAF8ZAbgBQC0JFgALPgLyiJxeEjgB+AHRVqJ5m0pygA

Working as Intended

Most helpful comment

also with any you could do prop.key.value which might not be safe and it would be a nice feature if you could do optional chaining without types but you would get an error without the questionmarks...

All 13 comments

seems like you're misunderstanding what unknown does

As a type, unknown means that the type is well, unknown. When you declare an object as unknown, Typescript gives up any type inference whatsoever and decides that the type must be cast before the variable can used.

So you need to actually tell TS that prop has a key, which has a value:

const helloworld = (prop as {key?:{value:string} } | undefined)?.key?.value
// helloworld is now string | undefined

that's intentional design for unknown to force the developer to reason about the value's typing before using it. You _could_ use any, but I feel you'd be shooting yourself on the foot there since any simply ignores typing altogether.

Well I do understand what unknown does,
however I expected this to work, because writing a typeguard or casting the value feels cumbersome if you need the value only once. And since optional chaining is a save way to access the properties if they exist, in my opinion it should be possible as an easy way to say "gimme that if it's there", without the need of explicit typing.
I understand that it is intentional though, otherwise it would behave too similar to any I guess...

also with any you could do prop.key.value which might not be safe and it would be a nice feature if you could do optional chaining without types but you would get an error without the questionmarks...

This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

I think it should be possible to do optional chaining on unknown.

What's really annoying is that it works perfectly fine at runtime. helloworld will be initialized with "Hello World"

As a type, unknown means that the type is well, unknown. When you declare an object as unknown, Typescript gives up any type inference whatsoever and decides that the type must be cast before the variable can used.

@Barbiero then why can I use the typeof operator on an unknown type? To verify the type maybe? Right, this is exactly what the optional chaining operator is for, too.

As a type, unknown means that the type is well, unknown. When you declare an object as unknown, Typescript gives up any type inference whatsoever and decides that the type must be cast before the variable can used.

@Barbiero then why can I use the typeof operator on an unknown type? To verify the type maybe? Right, this is exactly what the optional chaining operator is for, too.

Unknown can be any type, including non-objects. Consider

const prop: unknown = 4

prop?.something // error!

Optional chaining won't solve this.

@Barbiero but this is exactly what we are complaining about. It should not be an error because in your example it would help to verify that prop has not the type of {prop: {something: 3}} for example. Unknown should act with regards to the optional chaining operator exactly like the any type does:

const prop: any = 4
prop?.something // no error!

I guess my main point is the following. To use one of JavaScript's best operators to validate types I am forced to operate this operator on the any type although this should be exactly a case for unknown. Because like you said with regards to unknown correctly before:

the type must be cast before the variable can used

I would love to use optional chaining exactly for that. But I understand that the TypeScript team has to make compromises, of course.

@timonson how is your example "no error" when it will cause a runtime error?

The point of unknown is that you must type check before using it, because you don't know what that type is. prop?.something, when the underlying value is number, is a runtime error. unknown protects you against that.

Optional chaining isn't even relevant here. Its purpose is to protect you against _potentially null or undefined values_. What you are asking is that optional chaining forgoes any type checking at all.

What you want is to use the type any. And in that case, you need to pay attention to possible runtime errors. If you want both a top level type and compile-time error checking, then you use unknown - in which case, you must either type cast or type check(with a guard, for example).

@Barbiero what prevents me from using optional chaining right on underlying value? I would expect the following to work since I am considering the fact that variable can be undefined.

const variable = undefined as unknown;
if (variable?.prop?.something) {
  // do stuff
}

Javascript equivalent works:

const variable = undefined;
if (variable?.prop?.something) {
  // do stuff
}

And there's no error: I just executed the snippets in TypeScript Playground if I change to any (1) and Chrome console (2). You may have to read up on optional chaining.

@Barbiero it does NOT cause a runtime error.

Take a look at this example module please: https://github.com/timonson/deno/blob/dc95ec020d90381412e6ff9145a3f9feda1074b0/std/jwt/validation.ts

I use various type predicate functions there. For example:

function isTokenObject(obj: any): obj is TokenObject {
  return (
    typeof obj?.signature === "string" &&
    typeof obj?.header?.alg === "string" &&
    (typeof obj?.payload === "object" && obj?.payload?.exp
      ? typeof obj.payload.exp === "number"
      : true)
  );
}

These functions are used to validate types and I use optional chaining operators.

If I would change the parameter types to unknown I would have to evaluate much more expressions because I could not use the optional chaining operator.

I don't understand why you are trying to use unknown on either of these cases. If you know what type it is(which means you know the type's probable representation), then you don't use unknown. Unknown is a top level type meant to represent an.. unknown... Type. When you want to tell consumers of your functions that _pretty much anything can come out of the function_, or when you want to _enforce runtime type checking_.

If you know the type, then it's not unknown. Use your type accordingly. Make use of Partial<T> or | undefined. Optional chaining will be enabled, as expected.

When you declare something unknown, then Typescript can't infer it's type - because you just said it is not known. If you want the type to be inferred, you don't declare a type.

You're trying to use the wrong tool here.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

wmaurer picture wmaurer  路  3Comments

Roam-Cooper picture Roam-Cooper  路  3Comments

uber5001 picture uber5001  路  3Comments

siddjain picture siddjain  路  3Comments

MartynasZilinskas picture MartynasZilinskas  路  3Comments