Destino Dev

Blog

Converter JSON snake_case para camelCase com TypeScript e Generics

Por John Pires · 03 de abril de 2026

Converter JSON snake_case para camelCase com TypeScript e Generics

Por que aprender isso em 2026

Contexto prático

APIs externas ainda usam snake_case com frequência — especialmente em backends em Python ou bancos de dados legados. Frontends em TypeScript usualmente preferem camelCase nas propriedades. Converter automaticamente entre esses estilos reduz bugs, melhora autocompletar e mantém o contrato tipado entre servidor e cliente.

Analogia simples

Pense nas chaves do JSON como etiquetas de roupa com dois idiomas: snake_case é "linha de fábrica" e camelCase é "loja visual". Você quer um tradutor que funcione em ambos os sentidos, de modo que a loja mostre rótulos no idioma do frontend sem perder a validade do conteúdo.

Vantagens técnicas:

  • Autocompletion no editor funcionando corretamente.
  • Menos mapeamentos manuais e menos codigo repetido.
  • Contratos tipados entre runtime e tipos TypeScript com menor chance de erro.
Dica: Tipos nunca transformam runtime; combine transformação em tempo de execução com tipos que descrevem o resultado para segurança completa.

Configuração do ambiente

Ferramentas mínimas

Você precisa de Node.js (>=16 recomendado), npm ou pnpm, e TypeScript (>=4.5 para garantir suporte a template literal types). Um editor como VSCode melhora a experiência com autocompletion e jump-to-definition.

Configuração prática do tsconfig

Ative configurações que ajudam a trabalhar com features avançadas: template literal types, mapped types e possibilidade de decorators se optar por usá-los. Abaixo um exemplo mínimo. Isso não é TypeScript, mas é necessário; aplique no arquivo tsconfig.json.

// tsconfig.json (parcial)
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "CommonJS",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

Resultado: o TypeScript aceitará templates literais para tipos e permitirá decorators se você for usá-los.

Hello World progressivo

1) Converter uma chave isolada

Comece pequeno: uma função que transforma uma string de snake_case para camelCase. Essa função é a base para transformar chaves de objetos.

O código abaixo implementa a transformação de uma palavra e é totalmente funcional em TypeScript.

function snakeToCamel(s: string): string {
  return s.replace(/_([a-z0-9])/g, (_, p1) => p1.toUpperCase());
}

// Exemplo
console.log(snakeToCamel('first_name')); // output: firstName
console.log(snakeToCamel('user_id')); // output: userId

O que acontece depois: agora você tem uma função pura que converte rótulos. Ainda falta aplicar isso recursivamente em objetos ou arrays.

2) Converter um objeto raso

Aplicar a função acima às chaves de um objeto plano (sem objetos aninhados) é direto. Veja um exemplo de implementação que retorna um novo objeto com chaves convertidas.

function keysToCamel(obj: Record<string, any>): Record<string, any> {
  const result: Record<string, any> = {};
  for (const key in obj) {
    if (Object.prototype.hasOwnProperty.call(obj, key)) {
      result[snakeToCamel(key)] = obj[key];
    }
  }
  return result;
}

// Exemplo
const api = { first_name: 'Ana', last_name: 'Silva' };
console.log(keysToCamel(api)); // { firstName: 'Ana', lastName: 'Silva' }

Após executar, as chaves são convertidas e o valor permanece o mesmo. Isso serve para formulários simples e respostas não aninhadas.

3) Mostrar limites do método raso

Se o JSON tem arrays ou objetos aninhados, o método acima falha ao não descer na estrutura. A próxima etapa é uma função recursiva que trata objetos aninhados e arrays.

Mini-projeto prático

Objetivo do projeto

Construir uma função genérica que: receba um JSON em snake_case e retorne um objeto em camelCase, com tipos TypeScript que representem o novo formato. A meta é combinar runtime (conversão recursiva) e tipos (DeepCamelCase) para o editor saber o formato final.

Tipos TypeScript: SnakeToCamelCase e DeepCamelCase

Aqui entramos em generics avançados: um tipo que transforma strings e outro que aplica recursão em objetos e arrays.

type SnakeToCamelCase<S extends string> = S extends `${infer Head}_${infer Tail}`
  ? `${Head}${Capitalize<SnakeToCamelCase<Tail>>}`
  : S;

type DeepCamelCase<T> = T extends Array<infer U>
  ? Array<DeepCamelCase<U>>
  : T extends object
  ? { [K in keyof T as K extends string ? SnakeToCamelCase<K> : K]: DeepCamelCase<T[K]> }
  : T;

// Exemplos de tipos
type Api = { first_name: string; user_profile: { avatar_url: string } };
type Client = DeepCamelCase<Api>;
// Client será { firstName: string; userProfile: { avatarUrl: string } }

O que acontece depois: o TypeScript passa a entender que transformar um Api resulta em Client, e o editor fornece autocompletion com as chaves camelCase.

Função runtime recursiva com assinatura genérica

Agora ligamos os tipos ao runtime. A função abaixo faz a conversão de forma segura e retorna o tipo transformado usando uma asserção genérica. Ela percorre objetos, arrays e mantém primitivos inalterados.

function convertKeysToCamel<T>(input: T): DeepCamelCase<T> {
  if (Array.isArray(input)) {
    return input.map(item => convertKeysToCamel(item)) as unknown as DeepCamelCase<T>;
  }
  if (input && typeof input === 'object') {
    const result: any = {};
    for (const key in input as any) {
      if (Object.prototype.hasOwnProperty.call(input, key)) {
        const newKey = snakeToCamel(key);
        (result as any)[newKey] = convertKeysToCamel((input as any)[key]);
      }
    }
    return result as DeepCamelCase<T>;
  }
  return input as DeepCamelCase<T>;
}

// Uso prático
const apiResponse = {
  first_name: 'Joao',
  user_profile: { avatar_url: 'a.jpg', likes: [{ post_id: 1 }] }
};
const client = convertKeysToCamel(apiResponse);
// client.firstName ; client.userProfile.avatarUrl ; client.userProfile.likes[0].postId

Depois de executar, você terá um objeto com chaves camelCase e tipos que refletem essa transformação no TypeScript.

Erros comuns e armadilhas

Existem várias armadilhas quando se combina transformação runtime com tipos:

  1. Assumir que tipos mudam o runtime — tipos só existem no build time.
  2. Modificar objetos mutavelmente pode quebrar referências; prefira retornar novos objetos.
  3. Ignorar valores especiais como Date, Map, Set — a função acima trata apenas objetos planos e arrays.

Exemplo de bug clássico: se receber um campo null e você fizer typeof input === 'object' sem checar null, você pode iterar sobre null ou lançar erro. O código mostrado verifica input && typeof input === 'object' para evitar isso.

Dicas de performance

Para objetos muito grandes, conversão recursiva pode ser custosa. Algumas estratégias:

  • Converter apenas campos acessados (lazy conversion) — usar proxies para converter sob demanda.
  • Cachear transformações de um formato conhecido para reduzir trabalho repetido.
  • Executar conversão no servidor se muitos clientes consumirem o mesmo dado.

Recursos para continuar estudando

Leituras recomendadas

Estude template literal types e mapped types: esses são os pilares para manipular chaves em nível de tipo. Documentação oficial do TypeScript e posts técnicos sobre Type Challenges ajudam muito.

Ferramentas e bibliotecas

Algumas bibliotecas já resolvem partes do problema, como case-converter ou change-case, mas combinar com tipos TypeScript requer trabalho adicional. Use bibliotecas quando o desempenho e robustez forem cruciais.

Exemplo prático adicional: validação com type guards

Type guards verificam em runtime se um valor tem a forma esperada. Aqui vai um pequeno type guard para checar se algo é um objeto com chave específica.

function isObject(v: unknown): v is Record<string, unknown> {
  return v !== null && typeof v === 'object' && !Array.isArray(v);
}

// Uso
const data: unknown = JSON.parse('{"first_name":"Pedro"}');
if (isObject(data)) {
  const converted = convertKeysToCamel(data);
  // aqui o editor sabe que 'converted' tem propriedades camelCase (por inferência)
}

Depois: o type guard protege seu código antes de chamar funções que assumem objetos, evitando exceções inesperadas.

Conclusão prática

Resumo das etapas

Passos claros que você pode aplicar agora:

  1. Implemente e teste snakeToCamel para strings.
  2. Implemente a função recursiva que converte objetos e arrays.
  3. Adicione tipos genéricos SnakeToCamelCase e DeepCamelCase para descrever o resultado no TypeScript.
  4. Adote type guards para validar entradas desconhecidas.

Checklist rápido de produção

Antes de usar em produção, certifique-se de:

  • Tratar null e undefined.
  • Decidir o tratamento para Date/Map/Set e outros tipos especiais.
  • Medir impacto de performance em payloads grandes.

Feito isso, você terá um pipeline confiável que traduz formatos de API para o estilo do seu frontend, com tipagem que ajuda o desenvolvedor e reduz erros em tempo de execução.

Implementar essa estratégia é uma habilidade valiosa para quem está começando com TypeScript e quer entregar frontends mais previsíveis e seguros. O padrão de combinar runtime + tipos é recorrente em aplicativos complexos e vale a pena dominar.

Assuntos: TypeScript, generics, utility-types, snake_case, camelcase, type-guards

Leia também