Repository navigation
Allow more constructs to work as type guards for unknown #25720
Description
Activity
mattmccutchen commented
on Jul 17, 2018 ContributorMore actionsSure, why not!
- addedSuggestionAn idea for TypeScriptAn idea for TypeScriptAwaiting More FeedbackThis means we'd like to hear from more people who would be helped by this featureThis means we'd like to hear from more people who would be helped by this feature
on Jul 17, 2018 And one more thing:
let foo: unknown; if (typeof foo === "object") { // foo should probably be narrowed to {[prop: string]: unknown} here }
Reacted by Linus Unnebäck, James Bourne, Artem Tyurin, Ian Edington, Sindre Sorhus, Nicolas Carlo, Michael Ford, Mark Donnellon, Shaun Smekel, Daniel Holmes and 10 moreI was hoping
unknownwould let me have type-safe data-loading, e.g.:interface SomeValue { a: string, b?: number, c: 'left' | 'right' } function readValue(id: string): SomeValue { const u: unknown = await someDataSource(id); if ( typeof u !== 'object' || u === null || typeof u.a !== 'string' || b in u && typeof u.b !== 'number' || u.c !== 'left' && u.c !== 'right' ) { throw new Error(util.format('Invalid value with id %O from some data source: %O', id, u)); } return value; // TS is checking that the checks above actually verify the return type here }
This to me would be a better match to TS for what #26078 wants, but I wouldn't complain about adding quick-fixes to add the missing checks!
(remember that
typeof u === "object"should actually narrow toobject | null- Thanks javascript!)Reacted by James Bromwell, tu4mo and Takao BabaI would like it if type guards with
unknownworked a little more like this.let x: unknown if (typeof x === 'object' && x !== null && 'foo' in x && typeof x.foo === 'string') { /* x is promoted to {foo: string} here */ }I think the type promotion ought to work like so, if at all possible.
typeof unknown === 'object'->object | null(object | null) !== null->object'foo' in object->{foo: unknown}typeof {foo: unknown}.foo === 'string'->{foo: string}
Reacted by ma2saka, Jarrod Davis, Glen, Sindre Sorhus, Igor Morozov, Mick Dekkers, Mitch Ryan, omjadas, Rodrigo Tavares, Ivan Saranchonkau and 12 moreReacted by Jack Leigh, Justin Braithwaite, Jakub Wolny, Johannes Brosi, interphx, Artem Tyurin, Christopher Dignam, Tiago Moraes, michalszoradibb, Miloš Lajtman and 12 moreI realize I'm a bit late, but you might be interested in https://github.com/gcanti/io-ts -- provides a nice way to generate your necessary typeguards, though might be a bit heavy handed for the common usecase (and thus probably still worth considering this issue)
Reacted by Sergii LavrinThanks for the suggestion, but that's probably not relevant to the discussion.
I'd also like to add that
unknownValue instanceof Arrayshould really be refined tounknown[], notany[]as is the current behavior. I'm also not getting warnings about implicitanywhen I do that.Reacted by Yacine Hmito, Tristan MacKinlay, Sam A. Horvath-Hunt, Robin Howard, Sindre Sorhus, Scott Olson, Maxime Bernard, Anders Kjær Damgaard, James Bromwell and Vincent RiemerIs there any update regarding this issue? I would love to use the unknown type, but at this point it's just too verbose to narrow it down to bigger objects. This proposal would make it a lot easier.
Until this is fixed, this is a helper that can be used to make it easier to write manual type guards for
unknowntypes that you expect to be nested objects:export function isUnknownObject(x: unknown): x is { [key in PropertyKey]: unknown } { return x !== null && typeof x === 'object'; }Example usage:
function example(x: unknown) { if (isUnknownObject(x) && isUnknownObject(x.prop) && typeof x.prop.subProp === 'string') { console.log(x.prop.subProp); } else { console.log('Could not find subProp'); } } example({ prop: { subProp: 'test', } }); example({});For more complicated use cases, using something like https://github.com/gcanti/io-ts is probably a better option than writing the type checks manually, but
isUnknownObjectcan be useful for simple cases.Reacted by Chris, Thomas Junghans, AndreasGassmann, Mathieu Hofman, Nicolas Carlo, Bob D'Ercole, John Darryl Pelingo, Stephen Lautier, Mitch Ryan, NISHIZAWA Shuntaro and 10 moreReacted by Henrik Raitasola, Anders Kjær Damgaard, Ivan Saranchonkau, Jason Papakostas, Tushar Sharma, Nanjie Chen and Alexstephenlautier commented
on Oct 28, 2020 More actionsAdam Buechler (@butchler) Similar to what you suggested (infect i started with that)
function isAssumedType<T = Record<string, unknown>>(x: unknown): x is Partial<T> { return x !== null && typeof x === "object"; } // usage if (isAssumedType<CommandCreator>(arg) && arg.execute && arg.host) { return true; }
The main difference is that
argwill be partially typed so when you do checks with props they are bound to the interface so you can F2 rename safely and will also be updatedReacted by Mitch RyanisAssumedType<CommandCreator>(arg)This is effectively the same as a type assertion (i.e.
arg as Partial<CommandCreator>), but unlike a type assertion it does not use an explicitaskeyword and it implicitly changes the type ofargin the following expressions.Type assertions are fine and have to be used sometimes, but personally I would avoid using something like
isAssumedTypebecause 1) it is less explicit so other people reading the code might not realize a type assertion is being made and 2) it makes it very easy and convenient to use type assertions, which is probably a bad thing because type assertions should generally be avoided when possible.Reacted by Mitch Ryanstephenlautier commented
on Oct 29, 2020 More actionsYes, naming is not the best I agree (but whatever, you can call it as you want), and to be honest I only use it private in file along with some type guards so my main intention is usage in guards so far.
As you suggested
arg as Partial<CommandCreator>doesnt work inlined within the condition e.g.Whereas with my suggestion works as following:

So again, the main benefit is that F2 rename works (which is a huge plus imo)
Anyway just wanted to share.Reacted by Mitch Ryan and javascripterif (typeof x === 'object' && x !== null && 'foo' in x && typeof x.foo === 'string') {
/* x is promoted to {foo: string} here */
}typeof x.foo doesn't work -> Property 'foo' does not exist on type 'object'.
if (typeof x === 'object' && x !== null && 'foo' in x && typeof x.foo === 'string') {
/* x is promoted to {foo: string} here */
}typeof x.foo doesn't work -> Property 'foo' does not exist on type 'object'.
That's an example of something I would like to work, but currently doesn't.
if (typeof x === 'object' && x !== null && 'foo' in x && typeof x.foo === 'string') {
/* x is promoted to {foo: string} here */
}typeof x.foo doesn't work -> Property 'foo' does not exist on type 'object'.
That's an example of something I would like to work, but currently doesn't.
Maybe something like this works.. It does the job for me.. Here 'result' is of type unknown. So narrowing down to the required type using user defined type guard seems to be the sane type-safe solution. Or are there are other possibilities as well?
Would like to see this implemented.
function isTest(arg: unknown): arg is Test { return (typeof arg === "object") && (arg !== null) && ("quest" in arg) && (typeof arg.quest === "string"); } interface Test { quest: string; }Typescript still complains for the above code even though we check for all the cases before accessing the
questproperty.Using
anyinstead ofunknowntakes away the benefits of using TS in the first place.Reacted by Mitch Ryan, Ian VanSchooten, Tushar Sharma, Daniel Muller, ayame113 (val440), Lev Izraelit, Sojin Park, Alex Rattray, Tushar Sharma, Matthieu Bosquet and 14 moreI would expect
?.to work as well with anunknownerr, though perhaps there's a case I haven't thought of which would cause failure, eg:if (err?.message) { // err is typed as { message: unknown, [k: string]: unknown }, or similar. }
Reacted by Alexis Gadonneix, Vladyslav Huba, dfr-exnaton, iron-cherep, Jakob Guddas and Borui GuThe lack of better/easier type guards for
unknowntype is why I'm not using it in try...catch blocks as well. I should be able to do simplycatch (e: unknown) { console.log(e?.message); }
I don't want to overcomplicate my code just because
unknownis so hard to work with so I'm sticking toany, yet I'd happily useunknownif the code above and in the other proposed examples worked.Reacted by Maksim Kalinouski, Lukas Korte, dfr-exnaton, Thomas Queste, Soberia, iron-cherep, Mateusz Hadryś, Erwin Gaitan and Borui GuCrosslinking to #27706
Adding this kind of type of guards to Arrays, Maps and Sets could be done quite easily, Objects are be a bit more complicated.
const inp = process.env.INPUT as unknown const arr = ['a', 'b', 'c'] as const if (arr.includes(inp)) { console.log(inp) // ^? "a" | "b" | "c" } const obj = {'a':1, 'b':2, 'c':3} as const if (obj.hasOwnProperty(inp)) { console.log(inp) // ^? "a" | "b" | "c" } const map = new Map([['a',1], ['b', 2], ['c', 3]] as const) if (map.has(inp)) { console.log(inp) // ^? "a" | "b" | "c" } const set = new Set(['a', 'b', 'c'] as const) if (set.has(inp)) { console.log(inp) // ^? "a" | "b" | "c" }

Search Terms
unknown type guard
Related: #24439 (comment), #25172
Suggestion
Currently, only a very limited set of type guards are able to narrow the new
unknowntype:arg is any[]) and probably some more in the lib filesHowever to make working with unknown types less awkward, I'd like to see a couple of other constructs being able to narrow the
unknowntype:Use Cases
Make
unknowneasier to work with!Checklist
My suggestion meets these guidelines: