Converter JSON para uma struct em Go significa transformar um payload JSON de exemplo em um tipo Go com os tipos de campo corretos, nomes exportados e tags json: para que você possa fazer Unmarshal de forma limpa. Um gerador faz isso inspecionando o valor de cada chave e inferindo um tipo Go, depois mapeando o nome da chave JSON para um campo exportado em PascalCase com uma tag correspondente. Este guia mostra exatamente como essa inferência funciona, onde ela erra e como obter uma struct correta sem digitar manualmente uma única chave.
A maioria dos desenvolvedores encontra o mesmo problema: uma API retorna um objeto JSON com 40 chaves, e escrever a struct correspondente manualmente é lento e propenso a erros. Você esquece uma tag, adivinha int onde a API envia um float ou achata um objeto aninhado que deveria ser seu próprio tipo. O resultado é um Unmarshal em tempo de execução que silenciosamente descarta campos. A solução é entender as regras que um conversor aplica, para que você possa confiar em sua saída e corrigir os casos que ele não consegue adivinhar.
Como um Conversor de JSON para Struct Go Infere Tipos#
Um conversor percorre o JSON valor por valor. JSON tem apenas seis tipos de valor (string, número, booleano, objeto, array, null), mas Go tem muitos. O mapeamento é principalmente determinístico, com dois casos genuinamente ambíguos que você precisa conhecer.
Aqui está o mapeamento central que todo gerador usa:
| Valor JSON | Tipo Go inferido | Notas |
|---|---|---|
"olá" | string | Sempre inequívoco |
true / false | bool | Sempre inequívoco |
42 | int | Números inteiros padrão para int |
3.14 | float64 | Qualquer decimal se torna float64 |
{ ... } | struct aninhado | Torna-se seu próprio tipo nomeado |
[ ... ] | []T | Tipo do elemento inferido dos itens |
null | interface{} | Nenhuma informação de tipo disponível |
Os dois casos que merecem sua atenção são números e null. Todo o resto o conversor acerta sempre.
Por que int vs float64 é a decisão mais complicada#
JSON não tem tipo inteiro. O número 5 e o número 5.0 são ambos apenas "número" para um analisador JSON. Um gerador tem que adivinhar: se o literal não tem ponto decimal, ele escolhe int; se vê um ponto decimal, escolhe float64.
Essa heurística quebra de uma forma comum. Imagine um campo de API preco que é 100 no seu exemplo, mas geralmente é 100.50. O conversor vê um número inteiro e escreve int. A próxima resposta com 100.50 então falha ao desserializar para um int, ou pior, trunca.
Sempre verifique campos numéricos com a documentação da API, não apenas com sua amostra. Se um campo pode conter uma fração, mude o
intgerado parafloat64manualmente. Uma única amostra limpa não é um esquema.
Há também a opção json.Number. Se a precisão importa (IDs grandes, dinheiro, qualquer coisa que você não pode arredondar), decodifique para json.Number ou uma string e analise deliberadamente. Geradores padrão usam float64 para decimais, o que é bom para a maioria dos dados, mas perde precisão para IDs inteiros de 64 bits acima de 2^53.
Quando você obtém um tipo de interface vazia#
Se um valor JSON é null, o conversor não tem nada para inferir. Não há tipo, apenas a ausência de um. Então ele recai para interface{} (escrito any no Go moderno).
O mesmo fallback acontece para arrays de tipos mistos. Um array JSON como [1, "dois", true] não tem um único tipo de elemento, então o gerador produz []interface{}. Isso é tecnicamente correto, mas horrível de trabalhar, porque você perde toda a segurança de tipo em tempo de compilação e precisa fazer type assertion em cada elemento.
Quando você vê interface{} na saída gerada, trate como uma bandeira que diz "Não consegui descobrir isso, você decide." Geralmente a ação correta é fornecer ao conversor uma amostra mais rica onde esse campo está preenchido e então regenerar.
Tags de Campo, Nomes Exportados e omitempty#
Acertar os tipos é metade do trabalho. A outra metade são as tags json: e a nomeação dos campos, que é onde structs escritos à mão falham com mais frequência.
Por que os campos precisam ser exportados (capitalizados)#
O pacote encoding/json do Go só consegue ler e escrever campos exportados de structs, ou seja, campos cujo nome começa com letra maiúscula. Um campo não exportado (minúsculo) é invisível para Unmarshal e Marshal. Ele permanecerá silenciosamente em seu valor zero, sem erro.
Portanto, uma chave JSON user_name não pode mapear para um campo Go chamado user_name. O conversor renomeia para o exportado UserName e adiciona uma tag para fazer a ponte:
type User struct {
UserName string `json:"user_name"`
Email string `json:"email"`
}
A tag json:"user_name" informa ao pacote JSON: este campo exportado UserName corresponde à chave JSON user_name. Sem essa tag, o Go procuraria por uma chave chamada UserName (ele faz uma correspondência sem distinção de maiúsculas/minúsculas como fallback, mas confiar nisso é frágil). A conversão de snake_case ou camelCase para PascalCase mais uma tag explícita é exatamente o que um bom gerador automatiza.
O que omitempty realmente altera#
A opção omitempty aparece em tags como json:"email,omitempty". Ela afeta apenas a serialização (struct Go para JSON), nunca a desserialização. Ao codificar um struct, omitempty instrui o codificador a pular um campo inteiramente se seu valor for o valor zero (string vazia, 0, false, nil, slice ou mapa vazio).
Isso é importante para saídas de API. Compare o mesmo struct com e sem ela:
| Tag | Valor do campo | Saída JSON |
|---|---|---|
json:"middle_name" | "" (vazio) | "middle_name": "" |
json:"middle_name,omitempty" | "" (vazio) | (campo omitido) |
json:"age,omitempty" | 0 | (campo omitido) |
A armadilha com omitempty é que ele não consegue distinguir "intencionalmente zero" de "não definido." Se age for legitimamente 0 (um recém-nascido), omitempty o remove da saída. Quando você precisa distinguir "ausente" de "zero", use um campo ponteiro (*int), onde nil significa ausente e &zero significa um zero real.
A maioria dos conversores não adiciona
omitemptypor padrão, pois é uma escolha de serialização, não um fato do esquema. Adicione-o deliberadamente a campos que são genuinamente opcionais em sua saída, e recorra a campos ponteiro quando o valor zero for significativo.
Lidando com Estruturas Aninhadas e Repetidas#
Respostas reais de API raramente são planas. Elas aninham objetos dentro de objetos e carregam arrays de objetos. É aqui que escrever structs manualmente se torna realmente doloroso e onde um conversor mostra seu valor.
Quando um conversor encontra um objeto aninhado, ele tem duas estratégias:
- Struct inline (anônima): o tipo aninhado é escrito inline dentro de seu pai. Compacto, mas você não pode referenciar o tipo interno em outro lugar.
- Tipos nomeados (extraídos): cada objeto aninhado se torna seu próprio
structnomeado, referenciado pelo pai. Mais verboso, mas reutilizável e muito mais fácil de ler.
Para qualquer coisa além de um payload trivial, prefira tipos nomeados. Dado este JSON:
{
"id": 7,
"owner": { "name": "Ada", "verified": true },
"tags": ["go", "json"]
}
Um conversor usando tipos nomeados produz:
type Repo struct {
ID int `json:"id"`
Owner Owner `json:"owner"`
Tags []string `json:"tags"`
}
type Owner struct {
Name string `json:"name"`
Verified bool `json:"verified"`
}
Observe que tags se tornou []string porque cada elemento era uma string, e owner se tornou seu próprio tipo Owner. Para um array de objetos ("items": [ {...}, {...} ]), o conversor inspeciona o primeiro elemento (ou mescla entre elementos se for inteligente) e produz []Item com uma struct Item gerada.
O caso extremo de mesclagem de arrays#
Aqui está uma sutileza que geradores mais baratos perdem. Se um array contém objetos onde alguns itens têm um campo e outros não, um conversor ingênuo apenas lê o primeiro elemento e perde campos que aparecem depois. Um gerador robusto mescla chaves em todos os elementos do array para que a struct capture todos os campos, marcando aqueles que às vezes estão ausentes como candidatos para ponteiros ou omitempty. Se sua struct gerada está perdendo campos que existem mais profundamente em uma lista, essa é a causa.
Gere uma Struct Go a partir de JSON, Passo a Passo#
Você pode fazer isso no seu navegador em menos de um minuto, sem precisar do toolchain Go ou go install. O formatador e conversor de JSON gratuito da Molixa valida seu JSON primeiro e depois emite uma struct Go (junto com TypeScript, Zod e outros destinos) para que você identifique problemas de sintaxe antes que se tornem erros de compilação no Go.
Passo 1: Cole e valide seu JSON#
Insira uma resposta representativa no editor. A palavra mais importante aqui é representativa: use um exemplo com todos os campos preenchidos e valores realistas, não uma versão simplificada. Amostras vazias ou com muitos valores nulos são exatamente o que produz campos interface{}. O formatador sinaliza imediatamente vírgulas finais, aspas simples e chaves não escapadas, o que é muito menos doloroso do que corrigir uma falha de unmarshal no Go depois.
Passo 2: Gere a saída da struct Go#
Altere a saída para o destino de struct Go. O conversor percorre seu JSON, infere cada tipo, exporta e renomeia campos para PascalCase e escreve tags json: correspondentes. Objetos aninhados se tornam tipos nomeados e arrays se tornam slices automaticamente. Copie o resultado diretamente para seu pacote.
Passo 3: Corrija os campos ambíguos manualmente#
Esta é a etapa que os geradores não podem fazer por você, e pular é o motivo pelo qual as pessoas acham que os conversores não são confiáveis. Verifique a saída em busca de três coisas:
- Campos
intque podem conter decimais: altere parafloat64(oujson.Numberpara precisão). - Campos
interface{}: vieram denullou arrays mistos. Substitua por um tipo concreto ou forneça uma amostra mais rica e regenere. - IDs inteiros grandes: se um ID exceder 2^53, um
float64perderá precisão. Useint64,stringoujson.Numberdependendo da API.
Passo 4: Adicione omitempty e ponteiros onde necessário#
Decida, por campo, se ele é realmente opcional na saída. Adicione ,omitempty aos campos que devem desaparecer quando vazios. Para campos onde um valor zero é significativo (um 0 real, um false real), mude para um tipo ponteiro para que nil sinalize claramente "não fornecido". Em seguida, escreva um pequeno teste de Unmarshal com sua amostra real para confirmar que nada está sendo descartado silenciosamente.
Confiando na Saída: Verifique Antes de Enviar#
Uma struct gerada é um primeiro rascunho forte, não uma garantia. O fluxo de trabalho honesto é: gere, depois prove que ela funciona com seus dados reais. Faça unmarshal de uma resposta real na struct, faça marshal de volta e compare com o original. Campos ausentes ou reordenados aparecem na hora.
Se você também trabalha com TypeScript na mesma API, a mesma amostra JSON pode servir para ambos. Nosso guia sobre conversão de JSON para interface TypeScript aborda as regras paralelas de inferência de tipos no front-end, e se você estiver depurando o payload bruto primeiro, como formatar JSON em JavaScript explica a formatação bonita e as opções do JSON.stringify que tornam uma resposta bagunçada legível antes de você convertê-la.
A razão pela qual um conversor supera a digitação manual não é apenas a velocidade. Ele aplica a regra de exportação, o mapeamento de tags e o aninhamento de forma consistente toda vez, então a única coisa que sobra para você é o pequeno conjunto de decisões de julgamento (int vs float, opcional vs obrigatório, precisão) que nenhuma ferramenta pode inferir de uma única amostra. Essa divisão de trabalho é o ponto principal.
Conclusão: De JSON Bruto a uma Struct Go Confiável#
Converter JSON para uma struct Go é mecânico para a maioria dos campos e baseado em julgamento para alguns. Um gerador lida com os 90% mecânicos: exportar campos, escrever tags json:, nomear tipos aninhados e transformar arrays em slices. Você lida com os 10% que ele não consegue inferir, principalmente precisão numérica, campos interface{} derivados de null e se omitempty ou um ponteiro pertence a cada campo opcional.
Acertando essas regras, a conversão de JSON para struct Go se torna entediante da melhor forma. Cole uma amostra representativa no formatador JSON e conversor para struct Go, gere o tipo, corrija os poucos campos ambíguos e confirme que ele converte corretamente seus dados reais. Essa é a diferença entre uma struct que compila e uma em que você pode realmente confiar em produção.
Perguntas Frequentes#
Como converter JSON para uma struct em Go?
Cole uma amostra representativa de JSON em um conversor, que infere um tipo Go para cada valor, exporta e renomeia as chaves para PascalCase e adiciona tags json: correspondentes. Em seguida, revise manualmente campos numéricos e quaisquer tipos interface{} que a ferramenta não conseguiu inferir. Você pode fazer tudo gratuitamente no navegador com o formatador e conversor JSON da Molixa, sem precisar do toolchain Go.
Por que minha struct gerada tem campos interface{}?
Um campo interface{} (ou any) significa que o conversor não conseguiu inferir um tipo. Isso acontece quando o valor JSON era null ou quando um array continha tipos mistos. A solução é fornecer uma amostra onde esse campo esteja preenchido com um valor real e regenerar, ou substituir o interface{} pelo tipo concreto que você sabe que deveria ser.
Os campos de uma struct em Go precisam ser capitalizados?
Sim, se você quiser que encoding/json os leia ou escreva. O pacote JSON do Go só enxerga campos exportados, ou seja, aqueles que começam com letra maiúscula. Um campo em minúsculo é ignorado durante Marshal e Unmarshal sem erro, permanecendo silenciosamente em seu valor zero. Os conversores exportam todos os campos e usam a tag json: para mapear de volta ao nome original da chave.
O que faz o omitempty em uma tag JSON?
A opção omitempty afeta apenas a serialização (codificar uma struct para JSON). Ela instrui o codificador a pular um campo quando seu valor é o valor zero: string vazia, 0, false, nil, ou um slice ou map vazio. Não tem efeito na desserialização. Cuidado, pois ela não consegue distinguir um zero intencional de um campo não definido; portanto, use um tipo ponteiro quando o valor zero for significativo.
Devo usar int ou float64 para números JSON em Go?
Use float64 para qualquer campo que possa conter um decimal, e int apenas para valores que são sempre inteiros. Como JSON não tem tipo inteiro, os conversores adivinham a partir da sua amostra, então um campo que é 100 na amostra mas às vezes 100.50 recebe o tipo errado. Para IDs grandes ou valores monetários onde precisão importa, prefira int64, string ou json.Number para evitar arredondamento de ponto flutuante.
Como lidar com objetos JSON aninhados em uma struct Go? Converta cada objeto aninhado em seu próprio tipo de struct nomeado referenciado pelo pai, em vez de embutir tudo. Isso mantém o código legível e permite reutilizar o tipo interno. Um bom conversor faz isso automaticamente e também lida com arrays de objetos gerando um slice de uma struct nomeada, idealmente mesclando chaves de todos os elementos do array para que nenhum campo seja perdido.



