Introdução: por que as interfaces não funcionam durante a execução?
\n\nOlá, amigo! Se você já escreveu em TypeScript, certamente já se deparou com esta situação: você cria uma interface perfeita, descreve todos os campos, verifica os tipos na fase de compilação — e de repente percebe que em tempo de execução (runtime) seu código se comporta como se nenhuma interface existisse. Familiar? 🎯
\n\nEsta é uma das perguntas mais populares no Stack Overflow: "Interface type check with TypeScript" — milhares de visualizações, centenas de respostas. E não é surpresa, pois o TypeScript apaga as interfaces durante a compilação. Ao contrário das classes, as interfaces são uma construção puramente declarativa para o desenvolvedor e o compilador. Em JavaScript, elas não existem.
\n\nNeste artigo, vamos entender como contornar essa limitação, aprender a verificar se um objeto corresponde a uma interface em tempo de execução e responder às perguntas mais frequentes. Vamos lá! 🚀
\n\nPor que queremos verificar interfaces?
\n\nImagine: você recebe dados de uma API, de um formulário ou de um WebSocket. Você tem certeza de que eles correspondem à interface User, mas de repente chega um objeto sem o campo email ou com o tipo errado. Se não verificar — seu código vai falhar com um erro. As interfaces do TypeScript verificam apenas durante o desenvolvimento, não em produção. Portanto, são necessárias verificações em runtime.
O problema: interfaces não existem em tempo de execução
\n\nEm TypeScript, as interfaces são "açúcar sintático" para tipagem estática. Quando você compila o código, as interfaces desaparecem. Aqui está um exemplo simples:
\n\ninterface User {\n name: string;\n age: number;\n}\n\nconst data: unknown = JSON.parse('{"name": "Alice"}');\nif (data instanceof User) { // ❌ Erro! User é uma interface, não uma classe\n console.log(data.name);\n}\n\nO compilador dirá: 'User' only refers to a type, but is being used as a value here. Esta é a principal armadilha para iniciantes. Mas não se preocupe — existem várias maneiras comprovadas de contornar isso.
Método 1: Guardiões de tipo personalizados (User-Defined Type Guards)
\n\nA maneira mais popular e elegante é escrever uma função guardiã de tipo. Ela verifica a estrutura do objeto e retorna true/false, e o TypeScript "entende" que dentro do bloco com true o objeto corresponde exatamente à interface.
interface User {\n name: string;\n age: number;\n email?: string; // campo opcional\n}\n\nfunction isUser(obj: unknown): obj is User {\n if (typeof obj !== 'object' || obj === null) return false;\n\n const user = obj as Record<string, unknown>;\n return (\n typeof user.name === 'string' &&\n typeof user.age === 'number' &&\n (user.email === undefined || typeof user.email === 'string')\n );\n}\n\n// Exemplo de uso\nconst data: unknown = JSON.parse('{"name": "Bob", "age": 30}');\n\nif (isUser(data)) {\n // Agora o TypeScript sabe que data é User\n console.log(data.name.toUpperCase()); // ✅ Funciona\n}\n\nVantagens: controle total, tipos são inferidos corretamente, fácil de estender.
\nDesvantagens: precisa escrever manualmente para cada interface (mas existem bibliotecas!)
O que é obj is User?
\n\nEsta é a sintaxe do guardião de tipo. Ela diz ao TypeScript: "Se a função retornar true, então o argumento obj pode ser considerado do tipo User". Esta é a mágica que torna a verificação em runtime útil para análise estática.
Método 2: Usar classes em vez de interfaces
\n\nAs classes são a única construção do TypeScript que é preservada em JavaScript. Se você precisa verificar o tipo em tempo de execução, pode substituir a interface por uma classe com verificação através de instanceof.
class User {\n constructor(\n public name: string,\n public age: number,\n public email?: string\n ) {}\n}\n\nconst data = new User('Charlie', 25);\nif (data instanceof User) {\n consol