Fale conosco
Desenvolvimento

encoding/json/v2 estável no Go 1.27: chave duplicada e UTF-8 inválido agora dão erro

27 min de leitura
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

#Go #Go 1.27 #encoding/json #jsontext #JSON #backend #API #migração #arquitetura
Contato

Bora conversar?

Três campos. A resposta vem no WhatsApp.

Ou escreva para contato@maquinaweb.com.br.

Você vai para o WhatsApp com a mensagem pronta. Sem spam, sem lista.

Seus dados seguem a nossa política de privacidade.