Repository navigation
Description
Search Terms
Optional chaining, unknown
Suggestion
Currently it is forbidden to use optional chaining on unknown. But for all possible types in JavaScript, optional chaining will return undefined if it does not apply or cannot find the field. So we should safely regard optional chaining of unknown type to be unknown as well.
This is the verification of optional chaining on all possible types in JavaScript:
const num = 5;
const str = '6';
const undef = undefined;
const nul = null;
const bool = true;
const sym = Symbol('sym');
const obj = {};
const func = () => {};
console.log(num?.field); // print undefined
console.log(str?.field); // print undefined
console.log(undef?.field); // print undefined
console.log(nul?.field); // print undefined
console.log(bool?.field); // print undefined
console.log(sym?.field); // print undefined
console.log(obj?.field); // print undefined
console.log(func?.field); // print undefinedUse Cases
This is to simplify type checking on nested object with unknown type. For example, we should be able to do something like this:
function getFieldFromJson(json: unknown): undefined | number {
const field = json?.fieldA?.fieldB?.fieldC; // <--- this
if (typeof field === 'number') {
return field;
}
}Currently we will need to cast explicitly on someUnknown, someUnknown.fieldA, someUnknown.fieldA.fieldB and someUnknown.fieldA.fieldB.fieldC. People will find this is unnecessary verbose and may just fallback to use any.
Examples
As above
Checklist
My suggestion meets these guidelines:
- This wouldn't be a breaking change in existing TypeScript/JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
- This feature would agree with the rest of TypeScript's Design Goals.
Activity
[-]Optional chaining of unknown should be allowed[/-][+]Optional chaining of unknown should be unknown[/+]ilogico commentedon Mar 31, 2020
This is as safe as allowing property access on
{}(unknownis pretty much the same as{} | null | undefined) or allowing access to unknown properties in interfaces in general.I don't think you should be able to do something with a type and not with its sub-types (we already have
anyfor these things).And if this would be allowed, why should it be restricted to the optional chaining syntax?
I believe it is semantically equivalent.
RyanCavanaugh commentedon Mar 31, 2020
I think the real weirdness is that it'd break the operational directionality of
unknown. Normally for any typeSthat's a subtype ofT, if an operation is allowed onTthen it's definitely allowed onS, but nowunk?.propis valid when e.g.({ x: 1 })?.propisn't even though{ x: 1 }is by definition a subtype ofunknown.This would lead to some subtle breakage in the future, e.g. let's say you wrote
The proposal is to make this code OK, but then in the future it'd be a breaking change for us to narrow
xto{ y: unknown }in the body of theif, which seems bad - narrowing should only ever increase the set of things you're allowed to do.falsandtru commentedon Mar 31, 2020
Should use
x?.['y']instead.hktonylee commentedon Apr 1, 2020
Hi Ryan thank you for your excellent example. I agree that narrowing should only ever increase the set of things that are allowed to do. But I can think of a counterexample involving the use of contravariance.
As you see class narrowing does not always increase the set of actions that are allowed to do. I believe this
?.operator also faces similar situation.Beside theoretical issue, in practice use, people will just be fine to fix the optional chaining of a variable when they decide to narrow the type. Comparing to using
any, this feature will indeed benefit more than the harms.Regarding the breaking change concern, if this proposal is published along with the type narrowing of
unknownofinoperator, we should be free from introducing breaking change.omeid commentedon Dec 17, 2020
Isn't this also true for
any?I think you meant
console.log(x?.y)?It does increase it from
unknownto{ y: T }which means we can safely do things liketypeof x.ywhich wouldn't be possible onunknown.omeid commentedon Dec 17, 2020
For a concrete example, the famous react-router's
Routecomponent's children prop's location state can not be known and so it rightly sets thelocationparam toLocation<unknown>.Now referring to the state is pretty common, here is an example similar to the official documentation:
Unpacking
fromwith typescript whenstateis unknown becomes pretty cumbersome without allowing optional chaning to write:Or maybe there is a way to handle this simply without optional chaining on unknown?
omeid commentedon Dec 18, 2020
Gonçalo Fernandes (@ilogico)
Considering that this is possible with
any, why shouldn't it be possible withunknown? At least withunknownunlikeany, it is clear that nothing is known about the return value of this function and you must narrow the type before using it, unlike if it had returnedany.RyanCavanaugh commentedon Dec 18, 2020
Why should two types designed for completely opposite purposes (
anybeing the most-permissive,unknownthe least) behave the same?ljharb commentedon Dec 18, 2020
Ryan Cavanaugh (@RyanCavanaugh) becauae the entire purpose of
?.is to be able to navigate through all JS types - ie,x?.foois guaranteed to always beunknown, in JS, whenxisunknown. It may be that TS's design means that that's not a valid property to extract based on the type of the LHS and the name of the property on the RHS - but I'm not sure why that would result in anything but the same result asx.foo.In other words, the only difference between
x.fooandx?.fooshould be whenxcan be null or undefined - in which case,x?.fooresults inundefined. As it is now, TS codebases are forced to useanyinstead ofunknownwhen using optional chaining, which makes the code less type-safe.RyanCavanaugh commentedon Dec 18, 2020
Jordan Harband (@ljharb) I can't tell what you're trying to say with that.
If you desugared
into
TypeScript would error on that, same as we would error on
x.fooifxwereobjector{ }. Are you saying it should error, or should not error?It seems like "New syntax constructs should behave like their desugarings" would be an uncontroversial interpretation but that's not what I'm hearing here.
ljharb commentedon Dec 18, 2020
Ryan Cavanaugh (@RyanCavanaugh)
value.foo, specifically, would error, you're right, but let's say it wasvalue?.toString- that shouldn't error - it'd either beundefinedor a toString function.omeid commentedon Dec 19, 2020
anyisn't actually more permissive, it is TypeScript's coercion rules that allows you to use it as any type you like. Bothanyandunknownindicate lack of type information.The reason the parallel is important is because as already mentioned, with
unknownyou can communicate the unsafe nature of your code and afford a degree of type-safety that isn't possible withany. So it is important to improve the ergonomics of working withunknown. This is consistent with TypeScript design goal of ergonomics over soundness.24 remaining items
hraban commentedon Dec 24, 2021
Thanks for your reply, I just wanted to focus on the
toStirngpart: aren't typos in type declarations always a problem? If you force type declarations (e.g. Pimm "de Chinchilla" Hogeling (@Pimm)'s proposal) you end up shifting the typo from point of use to point of declaration.The comment you linked to (#46314) says short-circuiting would create the following risk:
Maybe someone has a more realistic example, because this is precisely the kind of code you use optional chaining to avoid. You'd write:
that's the point of null coalescing member access, as I understand it. But how would you do this currently? Cast to
any? Same problem. Type casting to a custom type with explicit checking? Same problem, right?I guess my question is: Is there a solution to handling
unknownin TS, currently, which is safer than optional chaining? I don't see optional chaining as a concession on any front, I'd love to understand what we'd be giving up.kralphs commentedon Dec 24, 2021
I was just saying that's the best argument against, not that I was a good argument against lol.
I'm personally advocating for optional chaining to be allowed on unknown for mathematical reasons. Set theory, the basis of the type system itself, and the nature of the
?.operator seem to indicate optional chaining should be allowed. It both provides justification for its use, and a concrete ground to argue against it being expanded to other operators, thus avoiding the slippery slope that gets bandied about.The case of typos is a valid concern, but it becomes a question of what's more important: a logically consistent type system or avoiding a class of run-time errors. The type system is supposed to help avoid the latter, but does that make it less important? I don't think so, but others clearly disagree.
But even the argument about avoidance of runtime errors has to be taken as what actually is going to prevent more run time errors, not what might. That ignores the human element. If more of the community stops casting to any because of optional chaining on unknown, that's safer code.
Jcparkyn commentedon Feb 8, 2022
For those interested, you can somewhat emulate this behaviour like this:
The caveats here are:
SafeAny(... as unknown as SafeAny)?.().typeofdoesn't work correctly on this type (technically it is correct, but because we're lying to the compiler it makes the wrong assumptions). You can work around this by using type guards instead.Alexandre-Fernandez commentedon Apr 29, 2022
this is how I manage this problem (feel free to use it or improve it, for now it only works for one level deep properties) :
yukinotech commentedon Jul 28, 2022
decodedData?.a?.b?.cshould beunknowninJSON.parse(),or it is not safeadrian-gierakowski commentedon Sep 30, 2022
Thanks for this Alexandre Fernandez (@Alexandre-Fernandez)!
I'm using it with a little tweak: instead of
I use:
which makes the following safer:
When using
object,subProp's type is implicitly assigned ananytype (which is obviously unsafe) and causes an error whennoImplicitAnyis enabled.zanminkian commentedon Aug 13, 2023
Code above will cause a compiling error in TS.
To solve this problem, I have to change
unknowntoany, or use type assertion. But I believe we should not encourage to useanyand type assertion in TS.NicholasAntidormi commentedon Jan 28, 2025
Disallowing optional chaining on
unknownreduces its utility and forces unnecessary boilerplate. There are many common scenarios where this limitation becomes frustrating. For example:Is it possible to reconsider this issue?
JuanGalilea commentedon Nov 7, 2025
Also it would solve issues like
being able to optional chain anything into any object would just reduce the painpoints caused by Object.keys not returning keyof type which Ryan Cavanaugh (@RyanCavanaugh) seems to have asked and answered in stackoverflow just to make it easier for devs struggling against this to find out why it is the way it is.
Given that
is the one that was decided to not be true (which is good), at least a change like this would make the struggle of dealing with it less painful.
grundb commentedon Feb 11, 2026
FWIW, here is a little helper function that just takes any value and performs a sequence of optional chaining property accesses on it (accepts any input and always produces
unknownresult):adrian-gierakowski commentedon Feb 11, 2026
That's a sledgehammer: you only want result to be unknown if you are chaining on an unknown.
grundb commentedon Feb 11, 2026
For sure. But it gets the job done for the common case of "I have an
unknownthing and I want anotherunknownthing"adrian-gierakowski commentedon Feb 11, 2026
LLMs can do better:
the only problem is that you don't get autocomplete for path elements
mfulton26 commentedon Feb 11, 2026
https://github.com/sindresorhus/dot-prop can be used to "get, set, or delete a property from a nested object using a dot path [or an array path]" that will return
undefinedfor nonexistent paths.glebbash commentedon Jun 3, 2026
Daniel Rosenwasser (@DanielRosenwasser) while I understand the architectural dead-ends and narrowing conflicts that led to this issue being closed, the underlying pain point remains a daily friction for many developers.
I agree that making
unknown?.fieldresolve tounknownglobally could introduce subtle bugs, but the current strictness forces developers into unsafe type-casting (as any) for incredibly standard JavaScript patterns.Context
Three quite common scenarios where this issue is a real pain is:
Error handling
Quite a lot of libraries set some kind of code on an error that you could use to check error type.
JSON.parseresults handling in untyped apisLibraries that accept
unknowntypePossible solutions
A restricted
dynamicorloose_unknowntypeA type that behaves like exactly like
unknown, but permits optional chaining/property access to yield furtherdynamictypes. This avoids the total type-check shutdown ofany.A localized compiler flag or block-level directive:
"allowOptionalChainingOnUnknown": trueor/* @ts-allow-in-next-block: unknown-optional-chaining */While
tsconfigflags aren't the best looking solution here (although this might be the simplest option to implement), allowing a block-scoped directive (similar to ESLint rules) to permit optional chaining onunknowncould localize the risk to just those error-handling or json handling blocks.There definitely is a middle ground where we don't have to choose between writing verbose (and sometimes runtime affecting) type-guards or completely surrendering to
anyjust to safely read a nested property.I hope this issue can be re-evaluated, I think enough people has raised a voice around this to signal that its often a real pain, especially in codebases that weren't written with TypeScript in mind and are gradually trying to get more type safety without testing every single change after typing existing code, because getting rid of
anys quite often requires runtime logic changes that are only needed to satisfy the TS compiler.