Am I wrong or does type-safety get thrown out in TypeScript when parsing JSON?
I should be getting an error here, but I don't:
interface Person {
name: string
}
const person: Person = somePossibleFalsey ? JSON.parse(db.person) : undefined
The above does not fail a type check, when I believe it should. The db.person
variable may not be present, which could render person
as undefined
. But Person
should not be undefined
. As far as I can tell this is because I'm using JSON.parse
.
Just to confirm that I should get an error, here is another snippet which correctly gives me an error:
const person: Person = Math.random() > .5 ? { name: 'Arthur' } : undefined
The above code produces the appropriate TypsScript error:
Type '{ name: string; } | undefined' is not assignable to type 'Person'.
Type 'undefined' is not assignable to type 'Person'.ts(2322)
Why is JSON.parse
allowing type-safety to fail? Or is there something else at play here?
I think the core of the question is why the second term of the ternary expression, undefined
, is allowed to be one of the alternatives that is assigned to Person
. This Github issue describes this as working as intended. The entire ternary expression gets the union of the first return type and the second, i.e. any | undefined
, which collapses to any
, which in turn makes it a valid assignment. (any
being the weakest type of all). Conversely, if this were to be wrapped in an if-else block, the error would go off:
let n: number;
let x: any = {}
n = x.z ? x.z : undefined // no error
let y: any = {}
if (y.z) {
n = y.z
} else {
n = undefined // error
}
(source)
I’m thinking the inconsistency here is that, in the first case, the entire ternary expression is inferred as any | undefined which collapses to just any; the compiler therefore sees a single return any and all is right with the world (even though it’s not). In the second case there are two return sites: one is return any which again is fine, and one is return undefined which is not.
I think the challenge is that a ternary isn't really a statement but an expression. An expression must have a single type; in this case that type happens to be any | undefined which is unsound simply by the nature of any. Changing this, I suspect, would be very difficult: what happens when you have a ternary in the middle of a statement, e.g. as an argument to a function? What happens when there are several ternaries in the same statement [...]
In order to type check the above the way you suggest, the compiler would basically have to duplicate the statement and individually type check it for every combination of true/false for every single ternary in the statement. You'd have a combinatorial explosion.
The current behavior is, I suspect, the best we're going to get.