encoding/json/v2 estável no Go 1.27: chave duplicada e UTF-8 inválido agora dão erro
encoding/json/v2 estável no Go 1.27: chave duplicada e UTF-8 inválido agora dão erro
Em 4 de novembro, o time do Go publicou o texto de referência sobre a nova API de JSON que acompanha o Go 1.27: encoding/json/v2 e encoding/json/jsontext passaram de experimento ( GOEXPERIMENT=jsonv2 ) a pacotes estáveis da biblioteca padrão. O anúncio fecha um esforço de design que começou em 2020 e resolve três problemas antigos do encoding/json : aceitar entrada malformada, não permitir streaming de verdade e depender de reflexão para tudo.
Por que isso importa para quem escreve código? Porque o encoding/json está no meio de quase todo serviço Go — entrada de API, webhook, mensagem em fila, arquivo de configuração. E boa parte dos comportamentos que a gente aprendeu a contornar (chave duplicada, maiúsculas que não batem, null onde deveria vir [] ) vai virar erro ou mudar de saída, dependendo do pacote que você importa.
O ponto mais importante e menos noticiado: nada quebra se você não migrar . O encoding/json clássico continua coberto pela promessa de compatibilidade do Go 1, e no 1.27 ele passou a rodar por baixo sobre o motor novo. A migração é uma decisão de contrato e de semântica, não de pressa.
Duas camadas em vez de um pacote só
A separação é o coração do desenho novo. encoding/json/jsontext cuida de sintaxe: tokens, valores crus, ordenação, indentação, decoder e encoder incrementais. encoding/json/v2 cuida de semântica: mapear struct , map , tags, interfaces de customização. Isso permite o mesmo parse alimentar um map[string]any , um validador de schema ou um binding direto em struct, sem reparsear o payload.
Na prática, o streaming deixa de ser gambiarra. Antes, para serializar uma lista grande sem alocar tudo em memória, era json.Encoder com Encode por item — e um [ escrito à mão. Agora:
O nome do arquivo é import "encoding/json/v2" e import "encoding/json/jsontext" . MarshalWrite , UnmarshalRead , MarshalEncode , UnmarshalDecode e as interfaces MarshalerTo / UnmarshalerFrom cobrem os casos em que hoje há io.Writer no meio do caminho.
O que muda no código que já existe
Quatro diferenças explicam quase todos os incidentes reais de migração.
1. Chave duplicada é erro. O v1 aplicava "o último vence", sem avisar. O v2 recusa.
Para autorização, isso não é detalhe de formatação: era a diferença entre dois componentes concordarem ou não sobre o que o payload dizia. O mesmo vale para UTF-8 inválido, que o v1 substituía por U+FFFD e o v2 rejeita.
2. Campos casam com maiúsculas. O v1 fazia match case-insensitive; o v2 é exato. Um parceiro que envia {"Id": "...", "Total": 10} deixa de preencher a struct — e, sem RejectUnknownMembers , não gera erro , só campos zerados. Esse é o tipo de bug que aparece em produção duas semanas depois. A saída é declarar a intenção na tag: json:"id,case:ignore" .
3. nil slice vira [] . Com var r Resposta , o v1 emitia {"itens":null} e o v2 emite {"itens":[]} . Melhor para consumo no front, mas muda snapshots, contratos publicados e testes que comparam string de saída.
4. omitzero no lugar do velho omitempty para tipos do Go. No v1, omitempty nunca omitia time.Time zero — saía "0001-01-01T00:00:00Z" no payload. O v2 traz omitzero , que usa a definição de zero do Go (inclusive método IsZero ). Em contrapartida, diferenças entre omitempty e omitzero mudam silenciosamente o formato de PATCH: campo que some do JSON passa a significar "não altere".
Em projetos onde a Maquinaweb atua com times de backend, esse tipo de mudança costuma ser tratada como mudança de contrato público: versão de API, teste de contrato e comunicação com quem consome, antes de trocar o import.
Velocidade é o argumento mais fraco
O título "até 10x mais rápido no Unmarshal " fez barulho, mas é preciso ler a letra miúda. Como o encoding/json v1 já roda sobre o motor v2 no 1.27, boa parte do ganho chega sem trocar uma linha . Benchmarks independentes mostram ganhos modestos (~10%) em structs comuns e ganhos grandes em padrões específicos, como decoders customizados recursivos. Ou seja: migrar apenas por performance tende a não pagar o risco. Migrar por parsing estrito, streaming real e opções explícitas, sim.
Riscos e armadilhas
Compatibilidade parcial não é o mesmo que compatibilidade. json.DefaultOptionsV1() reproduz o comportamento antigo, e é o caminho recomendado para começar: suba com as opções do v1, rode os testes, e depois afrouxe opção por opção.
MarshalIndent e json.Number não têm o mesmo lugar. Indentação virou opção do encoder ( jsontext.WithIndent ); quem dependia de json.Number precisa revisar como números arbitrários são lidos.
Tags de formato: tags novas de format no v2 podem gerar erro em código que ainda use o v1 com tag desconhecida. Vale um grep por json:" no repositório antes.
Duas gerações convivendo. v1 e v2 no mesmo binário funcionam, mas um tipo com MarshalJSON (v1) usado pelos dois caminhos merece teste — a customização precisa ser coerente.
Custo de adoção real: não é o import, é a revisão de snapshots, contratos e testes de integração com terceiros.
Recomendação prática
Trate como migração de contrato, não como upgrade de dependência. Comece por um serviço de borda (webhook, integração com parceiro), rode com DefaultOptionsV1() , adicione testes que comparem a saída byte a byte entre v1 e v2, e só então habilite o parsing estrito. Serviços internos que só consomem JSON de fontes confiáveis podem esperar; consumidores de dados externos são os que mais ganham.
Se o seu time está decidindo entre reescrever contratos de API, migrar stacks ou estruturar um serviço novo, o time da Maquinaweb pode ajudar a mapear o impacto antes da primeira linha de código — com engenharia sob medida para o seu contexto.
Fontes
Continue lendo
Desenvolvimento Polícia Civil de SP: 22 mil credenciais à venda por US$ 40 mil, Barracuda com root explorado e Nitori com 37,6 milhões
Grupo bl33dr00t põe 22 mil credenciais da Polícia Civil de SP à venda por US$ 40 mil enquanto a SSP nega invasão. Na mesma janela, a Barracuda confirma exploração de falhas que dão root em gateways ESG, a CISA corre contra o prazo de 13/11 e a Nitori expõe até 37,6 milhões de registros.
Ler artigo →
Desenvolvimento RHEL 10.3 sela o sistema com criptografia e o EPYC 9006 chega a 256 núcleos: a base física dos agentes de IA
Red Hat libera RHEL 10.3 e 9.9 com imagens seladas, MCP server GA para diagnóstico assistido e GCC 16. No mesmo dia, a AMD põe à venda o EPYC 9006 (Zen 6) com até 256 núcleos. E o Paraná usa IA do Google para encurtar a busca por terapia oncológica.
Ler artigo →
Desenvolvimento N-central fora do ar no mundo inteiro: 33 zero-days derrubam o RMM que administra o resto
N-able tirou o N-central hospedado do ar em todo o mundo depois que um pesquisador publicou 33 zero-days com PoC e lançou hotfix com 7 CVEs. Na mesma janela, o Citrus usa VLESS e LLMs em ataques, e a Fireworks AI confirma vazamento de datasets e pesos de modelos.
Ler artigo →