Everyday TypeScript: Object Values at Runtime
Welcome to the Object Values at Runtime lesson!
This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!
We've seen that TypeScript will let us narrow object types.
>
const user = {name: 'Amir', email: 'amir@example.com'};const hasEmail: {email: string} = user;hasEmail.email;Result:
'amir@example.com'
There's something very strange here, but it's not immediately obvious. When we compile TypeScript, we get JavaScript out. The TypeScript language was designed to make that transformation easy, so in most cases it only requires removing the type information. The compiler will compile our code above into approximately this JavaScript code:
>
const user = {name: 'Amir', email: 'amir@example.com'};const hasEmail = user;hasEmail.email;What value does
hasEmailhave? We never did anything to remove thenameproperty, sohasEmailstill has aname! Of course, TypeScript won't let us access it directly because it's not part ofhasEmail's type.>
const user = {name: 'Amir', email: 'amir@example.com'};const hasEmail: {email: string} = user;hasEmail.name;Result:
type error: Property 'name' does not exist on type '{ email: string; }'.Despite the type error, the
nameproperty is still there at runtime! We can access it by usingas anyto defeat the type system.>
const user = {name: 'Amir', email: 'amir@example.com'};const hasEmail: {email: string} = user;(hasEmail as any).name;Result:
'Amir'
This may seem like an unimportant detail. If the type system doesn't normally let us access the extra property, does it really matter?
Unfortunately, it matters a lot! If we look at the entire
hasEmailobject, it will have bothnameandemail. That can be confusing during debugging: if we print an object, for example withconsole.log(...), it might have many unexpected properties.>
const user = {name: 'Amir', email: 'amir@example.com'};const hasEmail: {email: string} = user;hasEmail;Result:
{name: 'Amir', email: 'amir@example.com'}Things get worse when we serialize our object into JSON. For example, we might use the built-in
JSON.stringifyfunction. ButJSON.stringifyis part of JavaScript, not TypeScript, so there's no way for it to know which properties it should serialize. It always serializes all properties, even properties that aren't in the type.>
const user = {name: 'Amir', email: 'amir@example.com'};const hasEmail: {email: string} = user;JSON.stringify(hasEmail);Result:
When we deserialize the JSON later, on the other side of a network connection for example, it will have both a
nameandemailproperty.>
const user = {name: 'Amir', email: 'amir@example.com'};const hasEmail: {email: string} = user;const stringified = JSON.stringify(hasEmail);JSON.parse(stringified);Result:
{name: 'Amir', email: 'amir@example.com'}This can cause serious real-world problems. For example, we might have a user object with many megabytes of data in its properties. Then we narrow its type down to just a couple of properties, like
{id: 1234, name: 'Amir'}, and finally convert it into JSON to send it to an API. But when we callJSON.stringifyon that object, it will still have those megabytes of unnecessary properties, causing a serious performance problem!This can also cause security problems. Our server-side database records might include sensitive data, like credit card information or personal contact information. If we narrow our records like we did for
hasEmailabove, it may look like those sensitive properties are gone. But at runtime they're still there, so our API server will return them to the client! Imagine viewing a user's profile on a site like GitHub, using your browser's developer tools to inspect the API payloads, and finding that user's credit card number!There are many ways to work around this problem, but your choices will depend on your technology stack and your team. However, it's important to keep the central idea of this lesson in mind: the TypeScript compiler checks the types, then throws them away and emits JavaScript code. TypeScript types can't affect runtime data.