¿Me equivoco o se descarta la seguridad de tipo en TypeScript al analizar JSON?
Debería estar recibiendo un error aquí, pero no:
interface Person { name: string } const person: Person = somePossibleFalsey ? JSON.parse(db.person) : undefined
Lo anterior no falla una verificación de tipo, cuando creo que debería. Es posible que la variable db.person
no esté presente, lo que podría convertir a person
en undefined
. Pero Person
no debe ser undefined
. Por lo que puedo decir, esto se debe a que estoy usando JSON.parse
.
Solo para confirmar que debería recibir un error, aquí hay otro fragmento que correctamente me da un error:
const person: Person = Math.random() > .5 ? { name: 'Arthur' } : undefined
El código anterior produce el error TypsScript apropiado:
Type '{ name: string; } | undefined' is not assignable to type 'Person'. Type 'undefined' is not assignable to type 'Person'.ts(2322)
¿Por qué JSON.parse
permite que falle la seguridad de tipo? ¿O hay algo más en juego aquí?
JSON.parse
devuelve un any
. Y dado que any
contiene cualquier tipo (incluido el indefinido), any | undefined
es lo mismo que any
.
Use as
para escribir el resultado JSON.parse
y obtendrá el resultado esperado:
const person: Person = db.person ? JSON.parse(db.person) as Person : undefined; // Error: Type 'Person | undefined' is not assignable to type 'Person'. Type 'undefined' is not assignable to type 'Person'
EDITAR: Parece que no tienes claro el any
. Esto esencialmente apaga todo tipo de seguridad. Cada tipo se puede asignar a any
y any
se puede asignar a cada tipo. Cuando tu lo hagas
const myAnyFunc = (): any => {return undefined;}; const myPerson: Person = myAnyFunc();
esto no crea un TypeError. No hay nada especial en JSON.parse()
, solo un 'problema' con cualquier cosa que devuelva any
. Eche un vistazo a este excelente libro de TS para obtener más información sobre any
.
Creo que el núcleo de la pregunta es por qué se permite que el segundo término de la expresión ternaria, undefined
, sea una de las alternativas asignadas a Person
. Este problema de Github describe que esto funciona según lo previsto. La expresión ternaria completa obtiene la unión del primer tipo de retorno y el segundo, es decir any | undefined
, que colapsa a any
, lo que a su vez la convierte en una asignación válida. ( any
es el tipo más débil de todos). Por el contrario, si esto se envolviera en un bloque if-else, el error se dispararía:
let n: number; let x: any = {} n = xz ? xz : undefined // no error let y: any = {} if (yz) { n = yz } else { n = undefined // error }
( fuente )
Estoy pensando que la inconsistencia aquí es que, en el primer caso, la expresión ternaria completa se infiere como cualquier | indefinido que colapsa a cualquiera; el compilador, por lo tanto, ve un solo retorno cualquiera y todo está bien con el mundo (aunque no lo esté). En el segundo caso, hay dos sitios de devolución: uno es return any, que nuevamente está bien, y otro es return undefined, que no lo está.
Creo que el desafío es que un ternario no es realmente una declaración sino una expresión. Una expresión debe tener un solo tipo; en este caso ese tipo pasa a ser any | indefinido que no es sólido simplemente por la naturaleza de cualquiera. Cambiar esto, sospecho, sería muy difícil: ¿qué sucede cuando tienes un ternario en medio de una declaración, por ejemplo, como argumento de una función? ¿Qué sucede cuando hay varios ternarios en la misma declaración [...]
Para escribir la verificación anterior de la manera que sugiere, el compilador básicamente tendría que duplicar la declaración y escribirla individualmente para verificar cada combinación de verdadero/falso para cada ternario en la declaración. Tendrías una explosión combinatoria.
El comportamiento actual es, sospecho, lo mejor que vamos a conseguir.