Marshal e Unmarshal
json.Marshal transforma um valor Go em bytes JSON. json.Unmarshal preenche um valor Go a partir de bytes JSON. As struct tags escolhem os nomes dos campos no JSON.
Email falta na primeira saída por causa do omitempty e de uma string vazia. O Unmarshal precisa de um ponteiro (&back); passar o próprio valor devolve um InvalidUnmarshalError.
Struct tags
A sintaxe da tag é json:"nome,opção,opção".
| Tag | Efeito |
|---|---|
json:"user_id" | usa user_id como chave |
json:"email,omitempty" | omite quando for false, 0, "", nil ou um slice ou map vazio |
json:",omitzero" (Go 1.24) | omite quando o valor é o valor zero, ou quando o IsZero() dele devolve true |
json:"-" | nunca codifica nem decodifica este campo |
json:"-," | usa a chave literal - |
json:"count,string" | codifica um número ou bool como string JSON ("42") |
| sem tag | a chave é o nome do campo em Go, UserID |
A senha nunca sai da struct, o saldo sai entre aspas, e deleted_at some enquanto created_at imprime o tempo zero 0001-01-01T00:00:00Z. Essa diferença é o motivo de o omitzero ter sido criado: o omitempty nunca funcionou para structs, e isso é uma surpresa antiga com time.Time. No Go 1.23 e anteriores, use um *time.Time com omitempty para ter o mesmo efeito.
MarshalIndent(v, prefix, indent) gera uma saída legível. Use para arquivos de configuração e depuração; APIs costumam enviar JSON compacto.
Só campos exportados
O encoding/json usa reflection e só enxerga campos exportados. Este é o bug de JSON mais comum em Go:
type point struct {
x, y int // lowercase: json.Marshal(point{1, 2}) gives {}
}
Nenhum erro, nenhum aviso, só {} na saída e campos que continuam zerados na entrada. Coloque os campos em maiúscula e acrescente tags para chaves em minúscula.
Como a decodificação casa os campos
No unmarshal para uma struct:
- As chaves são casadas com o nome da tag, ou com o nome do campo, sem diferenciar maiúsculas de minúsculas.
{"NAME": "x"}preenche um campo com a tagjson:"name". - Chaves sem campo correspondente são ignoradas em silêncio.
- Campos sem chave correspondente mantêm o valor atual. O Unmarshal não os zera, então decodificar em uma struct que já tem dados mescla com eles.
- Um tipo incompatível (uma string onde a struct tem um
int) devolve um*json.UnmarshalTypeError, mas os outros campos ainda são preenchidos.
Para rejeitar chaves inesperadas, por exemplo em uma API estrita ou em um arquivo de configuração com erros de digitação, use um Decoder com DisallowUnknownFields:
Estrutura desconhecida: map[string]any
Quando você não conhece o formato de antemão, decodifique em map[string]any ou any. O mapeamento é fixo:
| JSON | Go |
|---|---|
| objeto | map[string]any |
| array | []any |
| string | string |
| número | float64 |
| true / false | bool |
| null | nil |
Duas armadilhas aparecem na saída. Todo número é float64, então m["stock"].(int) falharia. E inteiros acima de 2^53 perdem precisão como float64: o id 9007199254740993 volta como ...992. O UseNumber evita as duas.
Se você conhece parte da estrutura, decodifique essa parte em uma struct e use json.RawMessage para o resto. Ele guarda os bytes brutos de um campo para você decodificá-lo depois, quando souber o tipo.
Slices, maps, ponteiros e nil
| Valor Go | JSON |
|---|---|
slice nil (var s []int) | null |
slice vazio ([]int{}) | [] |
map nil | null |
ponteiro nil | null |
[]byte | uma string em base64 |
map[string]T | um objeto, com as chaves ordenadas |
map[int]T | um objeto com as chaves inteiras como strings |
A diferença entre slice nil e vazio importa para clientes de API que esperam um array. Inicialize com []T{} ou make([]T, 0) quando o campo precisar ser [].
Use um campo ponteiro (*int, *bool) quando precisar diferenciar "ausente" de "zero" na entrada. Depois do unmarshal, um ponteiro nil significa que a chave faltava ou era null; um ponteiro para 0 significa que o cliente enviou 0.
Fluxos: Encoder e Decoder
json.Marshal e Unmarshal trabalham com slices de bytes inteiros. Para um io.Reader ou io.Writer (um corpo HTTP, um arquivo, o stdin), use json.NewDecoder e json.NewEncoder. Um Decoder também consegue ler uma sequência de valores JSON, um de cada vez:
O Encoder.Encode escreve uma quebra de linha depois de cada valor. Por padrão, tanto o Marshal quanto o Encoder escapam <, > e & como \u003c, \u003e e \u0026, para que o JSON possa ser embutido com segurança em HTML. SetEscapeHTML(false) desliga isso. Em handlers HTTP, json.NewDecoder(r.Body).Decode(&v) e json.NewEncoder(w).Encode(v) são a dupla de sempre.
Codificação personalizada
Um tipo pode controlar o próprio JSON implementando json.Marshaler e json.Unmarshaler. Um caso comum é um enum com iota que deve aparecer como nome, não como número:
MarshalJSON tem receiver de valor, então funciona tanto para valores quanto para ponteiros; UnmarshalJSON precisa de receiver ponteiro porque modifica o valor. Tipos que só precisam de uma forma em string podem implementar encoding.TextMarshaler (MarshalText), o que também permite usá-los como chaves de map.
Erros comuns
- Nomes de campo em minúscula. Eles são pulados em silêncio.
- Passar um valor para o
Unmarshal. Ele precisa de um ponteiro. - Ignorar o erro. JSON malformado e tipos incompatíveis só são reportados por meio dele.
- Supor que os números em
map[string]anysãoint. Eles sãofloat64. - Esperar que o
omitemptydescarte uma struct vazia ou umtime.Timezero. Useomitzerono Go 1.24, ou um ponteiro. - Depender da ordem dos campos em maps. As chaves de map saem ordenadas; os campos de struct mantêm a ordem de declaração.
Perguntas frequentes
Como converter uma struct em JSON em Go?
Chame json.Marshal(v), que devolve []byte e um erro. Só os campos exportados (nomes que começam com letra maiúscula) entram. Use uma struct tag como json:"name" em um campo para controlar a chave JSON dele. Para saída indentada, use json.MarshalIndent(v, "", " ").
Por que os campos da minha struct somem da saída JSON?
O encoding/json só enxerga campos exportados. Um campo chamado name (minúsculo) é invisível para ele, tanto no marshal quanto no unmarshal. Coloque o campo em maiúscula e defina a chave JSON com uma tag como json:"name".
O que o omitempty faz no JSON do Go?
O omitempty deixa um campo fora da saída quando ele guarda um valor vazio: false, 0, "", um ponteiro ou interface nil, ou um slice ou map vazio. Ele não considera vazia uma struct nem um time.Time. O Go 1.24 trouxe o omitzero, que omite qualquer valor igual ao valor zero do seu tipo (ou cujo método IsZero() devolve true), incluindo structs e time.Time.
Como interpretar um JSON de estrutura desconhecida em Go?
Faça unmarshal em map[string]any (ou any). Objetos viram map[string]any, arrays []any, strings string, booleanos bool e todo número float64. Use type assertions para ler os valores, e Decoder.UseNumber se inteiros grandes precisarem continuar exatos.