Serialización JSON personalizada de encoding/json
Cuando las etiquetas de struct no pueden expresar la forma JSON que necesitas, implementa json.Marshaler y json.Unmarshaler en tus tipos.
Busca en todas las páginas de la documentación
Cuando las etiquetas de struct no pueden expresar la forma JSON que necesitas, implementa json.Marshaler y json.Unmarshaler en tus tipos.
La serialización personalizada controla los formatos de cable para fechas, enums, vistas redactadas y campos calculados sin filtrar detalles de implementación.
El codificador de Go verifica cada valor en busca de MarshalJSON() ([]byte, error) antes de usar las reglas de struct predeterminadas.
El decodificador llama a UnmarshalJSON([]byte) error en los punteros que implementan la interfaz.
Los tipos incrustados y los receptores de puntero interactúan con las reglas de promoción, por lo que los pares simétricos de marshaling/unmarshaling y las pruebas de tabla mantienen las APIs honestas.
Tarjeta de receta de referencia rápida, lista para copiar y pegar.
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("estado desconocido %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("estado inválido %q", raw)
}
return nil
}Cuándo usar esto:
time.Time necesita un diseño que no sea 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))
}Lo que esto demuestra:
APIKey incrustados se promueven al JSON de Token a menos que se sombreen.MarshalJSON redactado oculta secret mientras lo acepta en la decodificación.ExpiresAt utiliza la serialización predeterminada de time.Time (RFC3339).json.Marshal recorre el árbol de valores.
En cada nodo pregunta:
json.Marshaler?Los marshaler personalizados devuelven fragmentos JSON completos, incluidas las comillas para las cadenas.
No codifiques dos veces: devuelve []byte("open") para una cadena, no json.Marshal("open") envuelto dos veces.
UnmarshalJSON recibe los bytes JSON sin procesar solo para ese valor.
Usa un receptor de puntero para que el decodificador pueda mutar el destino.
Definir un type alias struct { ... } interno copia los campos sin métodos.
Realiza marshaling a través del alias para obtener el comportamiento de struct predeterminado con diferentes opciones:
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,
})
}Si un struct externo incrusta un tipo con MarshalJSON, el método promovido codifica el valor incrustado cuando el struct externo no define el suyo propio.
Cuando tanto el externo como el interno definen marshaler, el externo gana la llamada de marshaling del tipo externo.
Para incrustar campos dentro de un objeto JSON padre, los campos del struct incrustado se aplanan a menos que el tipo incrustado implemente MarshalJSON (entonces se convierte en un solo valor JSON).
// Receptor de puntero vs. valor:
// - MarshalJSON en valor: funciona tanto para campos de valor como de puntero
// - UnmarshalJSON DEBE estar en el receptor de puntero
var s Status
json.Unmarshal(data, &s) // llama a (*Status).UnmarshalJSONPrefiere devolver errores tipados de UnmarshalJSON para que los manejadores se mapeen a respuestas 400.
User dentro de User.MarshalJSON sin un alias vuelve a entrar en el mismo método. Solución: usa el truco del struct de alias.open sin comillas produce JSON inválido. Solución: devuelve bytes JSON completamente entre comillas o llama a json.Marshal una vez en un primitivo.UnmarshalJSON en *T.null o omite en las etiquetas de struct padre con punteros.MarshalJSON rompe las pruebas de ida y vuelta. Solución: implementa ambos o documenta la codificación unidireccional.| Alternativa | Usar cuando | No usar cuando |
|---|---|---|
| Solo etiquetas de struct | Renombrar campos, omitempty, ocultar | Cadenas de enums, campos calculados |
json.RawMessage | Cargas útiles anidadas polimórficas | Formatos escalares simples |
Tipo envoltorio (type UserID string) | Validación de newtype en los límites | Structs grandes con muchos campos |
map[string]any | Prototipado rápido | APIs públicas estables |
| Biblioteca JSON de terceros | Necesita reflexión más rápida | Se requiere el comportamiento de la biblioteca estándar |
Sí, en tipos alias o primitivos, una vez por método.
Nunca llames a json.Marshal en el mismo tipo receptor sin un alias.
Sí, para registros de auditoría de solo escritura, pero las APIs que aceptan el mismo tipo también deberían implementar UnmarshalJSON.
Devuelve los bytes null de MarshalJSON cuando el valor está ausente.
Empareja con campos de puntero en structs padre para la semántica de omitempty.
Los punteros nil se codifican como null sin llamar al método.
Los punteros no nil llaman al método en el valor al que apuntan.
encoding/json también admite TextMarshaler para claves y algunos escalares.
Prefiere las interfaces JSON cuando el formato de cable es específico de JSON.
Pruebas de tabla de ida y vuelta: marshaling, unmarshaling, comparación.
Agrega casos para cadenas de entrada inválidas y valores cero.
Los métodos se enlazan a tipos instanciados normalmente.
Define marshaler en el struct genérico o en parámetros de tipo con restricciones según sea necesario.
json.Encoder llama a MarshalJSON por valor de la misma manera que Marshal.
Los arrays grandes aún se benefician de la transmisión en ambos casos.
Gin utiliza encoding/json internamente para los cuerpos JSON.
Los unmarshalers personalizados se ejecutan durante ShouldBindJSON.
A menudo sí: un UserLogDTO evita fugas accidentales de secretos.
Los marshaler personalizados en tipos de dominio funcionan cuando realmente necesitas un tipo en todas partes.
Versiones de Stack: Esta página fue escrita para Go 1.26.x (predeterminado de GC Green Tea, go fix modernizers - verifica el parche en la compilación), chi (última versión - verifica en la compilación), gin (última versión - verifica en la compilación), echo (última versión - verifica en la compilación), google.golang.org/grpc (última versión - verifica en la compilación), sigs.k8s.io/controller-runtime (última versión - verifica en la compilación), kubebuilder (última versión - verifica en la compilación), tinygo (última versión - verifica los objetivos de la placa en la compilación), wazero (última versión - verifica en la compilación) y golangci-lint (última versión - verifica el conjunto de linters en la compilación).
Revisado por Chris St. John·Última actualización: 18 jul 2026