encoding/json Marshaling Personalizado
Quando as tags de struct não puderem expressar a forma JSON que você precisa, implemente json.Marshaler e json.Unmarshaler em seus tipos.
Busque em todas as páginas da documentação
Quando as tags de struct não puderem expressar a forma JSON que você precisa, implemente json.Marshaler e json.Unmarshaler em seus tipos.
O marshaling personalizado controla formatos de fio para datas, enums, visualizações redigidas e campos computados sem vazar detalhes de implementação.
O codificador do Go verifica cada valor em busca de MarshalJSON() ([]byte, error) antes de usar as regras de struct padrão.
O decodificador chama UnmarshalJSON([]byte) error em ponteiros que implementam a interface.
Tipos incorporados e receptores de ponteiro interagem com regras de promoção, portanto, pares de marshaling/unmarshaling simétricos e testes de tabela mantêm as APIs honestas.
Cartão de receita de referência rápida - pronto para copiar e colar.
type Status int
const (
StatusOpen Status = iota + 1
StatusClosed
)
func (s Status) MarshalJSON() ([]byte, error) {
switch s {
case StatusOpen:
return []byte(`"open"`), nil
case StatusClosed:
return []byte(`"closed"`), nil
default:
return nil, fmt.Errorf("unknown status %d", s)
}
}
func (s *Status) UnmarshalJSON(b []byte) error {
var raw string
if err := json.Unmarshal(b, &raw); err != nil {
return err
}
switch raw {
case "open":
*s = StatusOpen
case "closed":
*s = StatusClosed
default:
return fmt.Errorf("invalid status %q", raw)
}
return nil
}Quando usar isso:
time.Time precisa de um layout não RFC3339.package main
import (
"encoding/json"
"fmt"
"time"
)
type APIKey struct {
secret string
prefix string
}
func (k APIKey) MarshalJSON() ([]byte, error) {
type alias struct {
Prefix string `json:"prefix"`
Hint string `json:"hint"`
}
return json.Marshal(alias{
Prefix: k.prefix,
Hint: k.prefix + "***",
})
}
func (k *APIKey) UnmarshalJSON(b []byte) error {
type alias struct {
Prefix string `json:"prefix"`
Secret string `json:"secret"`
}
var a alias
if err := json.Unmarshal(b, &a); err != nil {
return err
}
k.prefix = a.Prefix
k.secret = a.Secret
return nil
}
type Token struct {
APIKey
ExpiresAt time.Time `json:"expires_at"`
}
func main() {
t := Token{
APIKey: APIKey{secret: "supersecret", prefix: "sk_live"},
ExpiresAt: time.Date(2026, 12, 31, 0, 0, 0, 0, time.UTC),
}
b, err := json.Marshal(t)
if err != nil {
panic(err)
}
fmt.Println(string(b))
}O que isso demonstra:
APIKey incorporados são promovidos ao JSON Token, a menos que sejam substituídos.MarshalJSON redigido oculta secret, mas ainda o aceita na decodificação.ExpiresAt usa o marshaling padrão de time.Time (RFC3339).json.Marshal percorre a árvore de valores.
Em cada nó, ele pergunta:
json.Marshaler?Marshalers personalizados retornam fragmentos JSON completos, incluindo aspas para strings.
Não codifique duplamente: retorne []byte("open") para uma string, não json.Marshal("open") duas vezes.
UnmarshalJSON recebe os bytes JSON brutos apenas para esse valor.
Use um receptor de ponteiro para que o decodificador possa modificar o destino.
Definir um type alias struct { ... } interno copia campos sem métodos.
Faça o marshaling através do alias para obter o comportamento de struct padrão com opções diferentes:
func (u User) MarshalJSON() ([]byte, error) {
type alias User
return json.Marshal(struct {
alias
DisplayName string `json:"display_name"`
}{
alias: alias(u),
DisplayName: u.First + " " + u.Last,
})
}Se uma struct externa incorpora um tipo com MarshalJSON, o método promovido codifica o valor incorporado quando a struct externa não define o seu próprio.
Quando ambos, externo e interno, definem marshalers, o externo vence para a chamada de marshaling do tipo externo.
Para incorporar campos dentro de um objeto JSON pai, os campos da struct incorporada são achatados, a menos que o próprio tipo incorporado implemente MarshalJSON (então ele se torna um valor JSON).
// Receptor de ponteiro vs valor:
// - MarshalJSON em valor: funciona para campos de valor e ponteiro
// - UnmarshalJSON DEVE ser em receptor de ponteiro
var s Status
json.Unmarshal(data, &s) // chama (*Status).UnmarshalJSONPrefira retornar erros tipados de UnmarshalJSON para que os manipuladores sejam mapeados para respostas 400.
User dentro de User.MarshalJSON sem um alias reentra no mesmo método. Correção: use o truque da struct alias.open sem aspas produz JSON inválido. Correção: retorne bytes JSON totalmente entre aspas ou chame json.Marshal uma vez em um primitivo.UnmarshalJSON em *T.null ou omita em tags de struct pai com ponteiros.MarshalJSON quebra testes de ida e volta. Correção: implemente ambos ou documente a codificação unidirecional.| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
| Apenas tags de struct | Renomear campo, omitempty, ocultar | Strings de enum, campos computados |
json.RawMessage | Cargas úteis aninhadas polimórficas | Formatos escalares simples |
Tipo wrapper (type UserID string) | Validação de newtype nas fronteiras | Structs grandes com muitos campos |
map[string]any | Prototipagem rápida | APIs públicas estáveis |
| Biblioteca JSON de terceiros | Precisa de reflexão mais rápida | O comportamento da biblioteca padrão é necessário |
Sim, em tipos alias ou primitivos - uma vez por método.
Nunca chame json.Marshal no mesmo tipo receptor sem um alias.
Sim, para logs de auditoria somente gravação, mas APIs que aceitam o mesmo tipo devem implementar UnmarshalJSON também.
Retorne os bytes null de MarshalJSON quando o valor estiver ausente.
Combine com campos de ponteiro em structs pai para semântica omitempty.
Ponteiros nil são codificados como null sem chamar o método.
Ponteiros não nil chamam o método no valor apontado.
encoding/json também suporta TextMarshaler para chaves e alguns escalares.
Prefira interfaces JSON quando o formato de fio for específico de JSON.
Testes de tabela de ida e volta: marshal, unmarshal, compare.
Adicione casos para strings inválidas e valores zero.
Métodos se vinculam a tipos instanciados normalmente.
Defina marshalers na struct genérica ou em parâmetros de tipo com restrições, conforme necessário.
json.Encoder chama MarshalJSON por valor da mesma forma que Marshal.
Arrays grandes ainda se beneficiam do streaming de qualquer maneira.
O Gin usa encoding/json nos bastidores para corpos JSON.
Unmarshalers personalizados são executados durante ShouldBindJSON.
Muitas vezes sim - um UserLogDTO evita vazamento acidental de segredos.
Marshalers personalizados em tipos de domínio funcionam quando você realmente precisa de um tipo em todos os lugares.
Versões do Stack: Esta página foi escrita para Go 1.26.x (GC padrão Green Tea, go fix modernizers - verifique o patch na compilação), chi (mais recente - verifique na compilação), gin (mais recente - verifique na compilação), echo (mais recente - verifique na compilação), google.golang.org/grpc (mais recente - verifique na compilação), sigs.k8s.io/controller-runtime (mais recente - verifique na compilação), kubebuilder (mais recente - verifique na compilação), tinygo (mais recente - verifique os alvos de placa na compilação), wazero (mais recente - verifique na compilação) e golangci-lint (mais recente - verifique o conjunto de linters na compilação).
Revisado por Chris St. John·Última atualização: 18 de jul. de 2026