Menu

Omit em TypeScript: remover propriedades de um tipo

Omit<T, K> cria um tipo com todas as propriedades de T, exceto as chaves K. Veja Omit com uma e várias chaves, como sobrescrever o tipo de uma propriedade, remover a propriedade em tempo de execução, Omit vs Exclude e Pick, por que Omit aceita chaves que não existem, um Omit estrito e Omit em union types.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

Omit<T, K> é um utility type embutido que cria um tipo de objeto com todas as propriedades de T, exceto as chaves listadas em K. O uso clássico: um tipo User que tem um password, e uma versão pública que não tem.

PublicUser fica sincronizado com User: adicione um campo a User e ele aparece em PublicUser também, enquanto password continua de fora. Omit funciona só com tipos; é a desestruturação em toPublic que remove a propriedade do objeto de verdade.

Omitindo várias chaves

O segundo argumento é uma union, então liste quantas chaves precisar com |:

"O tipo sem os campos gerados pelo servidor" é o Omit mais comum em código real: um único tipo de modelo, com Omit derivando o formato de entrada para formulários de criação e requisições de API.

Como Omit é definido

Omit é montado a partir de dois outros utility types:

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

Leia de dentro para fora: keyof T é a union de todas as chaves, Exclude<keyof T, K> remove dessa union as indesejadas, e Pick mantém as chaves restantes. Então Omit<User, "password"> é Pick<User, "id" | "name" | "email">. Como Pick mapeia sobre as chaves de T, os modificadores readonly e ? das propriedades restantes são mantidos.

Repare na restrição: K extends keyof any, não K extends keyof T. Esse detalhe é o assunto da próxima seção.

Omit não verifica se a chave existe

keyof any é string | number | symbol, então Omit aceita qualquer chave, inclusive uma que T não tem. Um erro de digitação compila e não remove nada:

Nenhum erro em lugar nenhum, e a senha vai embora no JSON. A restrição frouxa foi escolhida de propósito quando Omit foi adicionado no TypeScript 3.5. Os motivos do time: em código genérico, as chaves omitidas muitas vezes não vêm de T (uma função que mescla dois objetos pode tipar o argumento rest como Omit<T1, keyof T2>), Exclude também não restringe o segundo parâmetro, e uma versão restrita teria quebrado muitos dos tipos Omit escritos à mão que os projetos já tinham. No código do dia a dia, uma versão mais estrita pega o erro de digitação:

index.ts(10,36): error TS2344: Type '"pasword"' does not satisfy the constraint 'keyof User'.
index.ts(11,7): error TS2741: Property 'password' is missing in type '{ id: number; name: string; }' but required in type 'PublicUser'.

O primeiro erro é o que importa: a chave é verificada contra keyof User. O segundo decorre dele, já que nada foi omitido e PublicUser ainda exige password. Corrija a grafia para "password" e compila. Muitos projetos definem esse tipo uma vez em um arquivo de tipos compartilhado e o usam no lugar de Omit.

Sobrescrevendo o tipo de uma propriedade

Uma interface não pode estender outra e mudar uma propriedade para um tipo incompatível: interface ApiUser extends User { id: string } falha com error TS2430: Interface 'ApiUser' incorrectly extends interface 'User'. Omita a propriedade primeiro e depois declare-a de novo:

Com um type alias, a mesma coisa é uma interseção: type ApiUser = Omit<User, "id"> & { id: string }. Sem o Omit, a interseção User & { id: string } daria a id o tipo number & string, que é never.

Omit em union types

Omit não é distributivo. Em uma union, ele parte do keyof da union inteira, que contém só as chaves que todos os membros compartilham, então as propriedades próprias de cada membro desaparecem:

index.ts(8,39): error TS2353: Object literal may only specify known properties, and 'radius' does not exist in type 'NewShape'.

NewShape virou { kind: "circle" | "square" }. Para omitir de cada membro separadamente, aplique Omit dentro de um conditional type distributivo:

T extends unknown ? ... : never é sempre verdadeiro; ele está ali só para fazer o conditional type se distribuir sobre a union. A página de conditional types explica por que isso funciona. Use DistributiveOmit sempre que o tipo de onde você omite for uma discriminated union.

Omitindo uma propriedade aninhada

Omit só olha para as chaves do primeiro nível, então Omit<Settings, "editor.wordWrap"> não remove nada (e, como mostrado acima, não reclama). Para tirar uma propriedade um nível abaixo, omita o pai e adicione-o de volta com o seu próprio Omit:

Settings["editor"] é um indexed access type: ele dá nome ao tipo do objeto aninhado para que Omit possa trabalhar com ele.

Removendo propriedades em tempo de execução

Omit muda só o tipo. Para produzir um objeto sem as propriedades, tire-as com desestruturação, ou escreva um pequeno helper genérico cujo tipo de retorno seja Omit:

O helper restringe K a keyof T, então uma chave com erro de digitação é erro no ponto da chamada. A cópia é rasa: objetos aninhados são compartilhados com o original.

Omit vs Pick vs Exclude

Funciona comMantémExemploResultado
Omit<T, K>tipos de objetotoda propriedade exceto KOmit<User, "password">{ id; name; email }
Pick<T, K>tipos de objetosó as propriedades em KPick<User, "id" | "name">{ id; name }
Exclude<U, M>union typesmembros da union não atribuíveis a MExclude<"a" | "b" | "c", "a">"b" | "c"

Escolha entre Omit e Pick pelo que deve acontecer quando o tipo de origem crescer. Omit inclui as propriedades novas automaticamente, o que combina com "tudo exceto o segredo". Pick nunca as inclui, o que combina com "exatamente estes campos" e é mais seguro para qualquer coisa que sai do seu servidor. Pick verifica as chaves contra keyof T; Omit não.

Perguntas frequentes

O que Omit faz no TypeScript?

Omit<T, K> monta um novo tipo de objeto com todas as propriedades de T, exceto aquelas cujas chaves estão em K. Omit<User, "password"> é User sem password. Ele só muda o tipo; não remove nada de um objeto em tempo de execução.

Como omitir várias propriedades no TypeScript?

Passe uma union de chaves como segundo argumento: Omit<User, "password" | "createdAt" | "updatedAt">. Toda chave da union é removida.

Qual a diferença entre Omit e Exclude?

Omit remove propriedades de um tipo de objeto: Omit<{ a: 1; b: 2 }, "a"> é { b: 2 }. Exclude remove membros de um union type: Exclude<"a" | "b", "a"> é "b". Omit é implementado com Exclude: Pick<T, Exclude<keyof T, K>>.

Por que Omit não dá erro para uma chave que não existe?

O parâmetro de chave dele é restrito a keyof any (qualquer string, number ou symbol), não a keyof T, então um erro de digitação como Omit<User, "pasword"> compila e não remove nada. Defina type StrictOmit<T, K extends keyof T> = Omit<T, K> para ter um erro com chaves desconhecidas.

Como remover uma propriedade de um objeto em tempo de execução no TypeScript?

Use desestruturação com um elemento rest: const { password, ...rest } = user;. O TypeScript tipa rest como o objeto sem password, o mesmo formato de Omit<User, "password">, e a propriedade realmente some do novo objeto.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR