Pular para o conteúdo principal

Converter JSON em Go

O JSON para Go gera structs a partir de um documento de exemplo no seu navegador. Todo campo é exportado e recebe uma tag json explícita, porque o encoding/json ignora em silêncio os campos não exportados — um nome de campo em minúsculas é, de longe, o motivo mais comum de uma struct gerada ser serializada como um objeto vazio.

Só local
JSON de exemplo
0 B1 linhaLin. 1, Col. 1
Erro de sintaxe · Erro na linha 1, coluna 1

O documento termina cedo demais — parece incompleto.


^
Código geradosomente leitura
0 B1 linha
Se possível, cole mais de um registro. Assim, campos opcionais ausentes em algumas linhas podem ser detectados.

Como funciona o Converter JSON em Go?

Três decisões determinam se uma struct de Go gerada fica correta ou quebra em silêncio, e as três dizem respeito ao que o encoding/json faz em tempo de execução, não à aparência do código.

Todo campo é exportado e recebe tag

O Go só serializa campos exportados. Uma struct com um campo chamado id produz {} a partir de json.Marshal e deixa id em zero depois de json.Unmarshal, sem erro em nenhum dos casos. Por isso todo nome é capitalizado e recebe uma tag json:"…" explícita com a chave original. As siglas seguem o guia de estilo do próprio Go: user_id vira UserID, não UserId, e api_url vira APIURL.

Campos opcionais são ponteiros

Um string simples não pode conter um null de JSON, e não consegue distinguir uma chave ausente de uma vazia — ambas chegam como "". Um int simples não distingue um count ausente de um count igual a zero. Por isso os campos opcionais e anuláveis viram ponteiros, a única construção em Go que preserva essa distinção. Slices, maps e interface{} já aceitam nil, então ficam como estão em vez de receber uma segunda indireção sem propósito.

Inteiros que int não comporta viram int64

int tem 32 bits em algumas plataformas. Um ID snowflake ou um timestamp em milissegundos a estoura, e o bug resultante só aparece na plataforma em que o tipo é estreito. Valores além de 2,147,483,647 são emitidos como int64.

Unions viram interface{}, porque Go não tem tipo soma

Um array que contém tanto strings quanto números não tem nenhum tipo de Go que o descreva. Geradores que inventam um produzem código que falha ao deserializar; interface{} é a resposta honesta, e o lugar para corrigir isso é a API, não a struct.

A saída já sai alinhada segundo o gofmt

Nomes, tipos, tags e comentários de fim de linha são preenchidos até colunas alinhadas. Sem isso, o primeiro gofmt no CI gera um diff em um arquivo que ninguém editou.

Exemplo

[
  { "user_id": 900719925474099, "api_url": "https://x.dev" },
  { "user_id": 12, "api_url": "https://y.dev", "note": "hi" }
]

Gerado

type User struct {
	UserID int64   `json:"user_id"`
	APIURL string  `json:"api_url"`
	Note   *string `json:"note,omitempty"` // optional
}

Quais opções e casos-limite o Converter JSON em Go suporta?

Referência técnica e opções
ParâmetroTipoPadrãoComportamento e casos-limite
PacoteidentificadormainA cláusula package no topo do arquivo. Defina como o pacote onde o arquivo de destino reside — models, api, internal/store.
ponteirosbooleanoonCampos opcionais e nulos se tornam *T, para que uma chave ausente seja distinguível do valor zero. Desligar isso torna o struct mais simples e torna os dados ausentes invisíveis.
omitemptybooleanoonAdiciona ,omitempty a campos opcionais para que eles desapareçam quando serializados de volta. Note que omitempty também descarta uma string vazia genuína ou zero, que é exatamente por que o ponteiro importa.
json.NumberbooleanooffEmite json.Number para não inteiros em vez de float64, preservando os dígitos literais. Vale a pena para valores monetários ou para IDs que chegam como números sem aspas além do intervalo exato do float64.
stringtipo GostringFormatos detectados (uuid, date-time, email, uri) são anotados em um comentário em vez de serem mapeados para um tipo, porque encoding/json não analisará um timestamp em time.Time sem um unmarshaller personalizado.
inteirotipo Goint / int64int por padrão; int64 quando qualquer amostra excede 2,147,483,647. Um ID truncado é um bug que só aparece em produção.
númerotipo Gofloat64Qualquer valor com parte fracionária. Se um inteiro e um float aparecem no mesmo campo entre registros, o campo se torna float64.
objeto aninhadotipo Go*NamedPromovido para seu próprio struct e referenciado por ponteiro, o que evita cópia na atribuição e permite que o campo seja nulo. Formas idênticas compartilham um struct.

Perguntas frequentes

Por que todo campo é capitalizado se minhas chaves JSON estão em minúsculas?

Porque o encoding/json só enxerga campos exportados, e exportar em Go significa começar com letra maiúscula. Um campo em minúsculas é ignorado tanto pelo Marshal quanto pelo Unmarshal, sem erro nenhum: a struct compila, roda e não faz nada. A tag json carrega a chave original, então o formato na rede não muda.

Preciso dos ponteiros?

Você precisa deles sempre que ausente e zero significarem coisas diferentes. Um *int com valor nil significa que a chave não veio; um int igual a 0 pode ser qualquer uma das duas coisas. Para um campo como retry_count ou discount, essa distinção normalmente importa. Para um campo que você sabe que sempre está presente, é ruído, e desligar os ponteiros é razoável.

Por que meu timestamp vira string em vez de time.Time?

Porque o encoding/json não converte por conta própria um timestamp arbitrário em time.Time — ele só trata RFC 3339, e só se o campo estiver declarado como time.Time, caso em que falha de vez com qualquer outro formato. Gerar time.Time a partir de um exemplo produziria uma struct que entra em pânico no primeiro registro que formatar a data de outro jeito. O formato detectado é anotado em um comentário para que você tome essa decisão com consciência.

O que uma interface{} está fazendo na minha struct?

Um campo continha tipos realmente diferentes entre os seus exemplos — um string em um registro, um objeto em outro — e Go não tem tipo que expresse isso. As alternativas são um UnmarshalJSON customizado, um json.RawMessage que você decodifica depois, ou consertar a API. Um tipo union gerado simplesmente falharia ao deserializar.

O gofmt vai alterar a saída?

Não deveria. Nomes de campo, tipos, tags e comentários de fim de linha são preenchidos até colunas alinhadas, que é exatamente o que o gofmt faz com uma struct. Se você encontrar um caso em que o gofmt ainda gera um diff, isso é um bug que merece ser reportado, não uma preferência de estilo.

Meu JSON é enviado?

Não. O parse, a inferência de tipos e a renderização rodam todos no seu navegador. Nada é enviado a lugar nenhum, e isso importa porque os exemplos que as pessoas colam em um gerador de structs costumam ser respostas reais de API de um sistema com o qual estão fazendo integração.