Comparação entre Go 1.25 encoding/json v1 e v2
O pacote encoding/json
v2 de Go é uma nova implementação que visa melhorar várias deficiências da versão v1 existente (falta de consistência, comportamento inesperado, problemas de desempenho). É um recurso experimental ativado pela tag de compilação goexperiment.jsonv2
.
O ponto mais importante é que quando o v2 é ativado, o v1 atua como uma camada de compatibilidade que emula o comportamento do v1 sobre a implementação do v2. Isso é feito através da função DefaultOptionsV1()
no arquivo v2_v1.go
. Ou seja, o v2 oferece opções para reproduzir completamente o comportamento do v1, ao mesmo tempo em que introduz um novo comportamento padrão mais rigoroso e previsível.
Os principais objetivos do v2 são os seguintes:
- Melhorar a precisão e a previsibilidade: Aplica regras mais rigorosas por padrão (por exemplo, diferenciação de maiúsculas e minúsculas, proibição de chaves duplicadas) para reduzir comportamentos inesperados.
- Melhorar o desempenho: O motor de parsing e codificação foi redesenhado para aumentar a eficiência.
- Aumentar a flexibilidade e o controle: Introduz um sistema de opções detalhado para permitir que os desenvolvedores controlem finamente como o JSON é processado.
Principais diferenças de significado/comportamento
As diferenças de comportamento entre v1 e v2 foram organizadas por item, com foco no arquivo v2_test.go
.
1. Correspondência de nomes de campos (diferenciação de maiúsculas e minúsculas)
- Comportamento do v1: Ao unmarshal membros de objetos JSON para campos de structs Go, a correspondência é não sensível a maiúsculas e minúsculas. Tanto
"FirstName"
quanto"firstname"
são mapeados para o campoFirstName
. - Comportamento do v2: Por padrão, a correspondência é sensível a maiúsculas e minúsculas, mapeando apenas campos que correspondem exatamente.
- Razão da mudança: A correspondência não sensível a maiúsculas e minúsculas pode causar comportamentos inesperados e degradação de desempenho ao lidar com campos não correspondentes. O v2 adota um comportamento mais claro e previsível como padrão.
- Opções relacionadas: No v2, a não diferenciação de maiúsculas e minúsculas pode ser explicitamente ativada por campo usando a opção de tag
json:"...,case:ignore"
, ou globalmente aplicando a opçãojson.MatchCaseInsensitiveNames(true)
.
2. Mudança no significado da opção de tag omitempty
- Comportamento do v1: Omite o campo com base no "estado vazio" do valor Go. "Estado vazio" significa
false
,0
, ponteiros/interfacesnil
, arrays/slices/maps/strings de comprimento zero. - Comportamento do v2: Omite o campo com base no "estado vazio" do valor JSON codificado. Ou seja, se for codificado como
null
,""
,{}
,[]
, ele é omitido. - Razão da mudança: A definição do v1 é dependente do sistema de tipos de Go. O v2 fornece um comportamento mais consistente com base no sistema de tipos JSON. Por exemplo, no v1, um valor
false
de tipobool
é omitido, mas no v2,false
não é um valor JSON vazio, então não é omitido. O v2 adicionou a opçãoomitzero
para substituir o comportamento doomitempty
do v1 que se aplicava a0
oufalse
. - Opções relacionadas: Se o comportamento do v1 for desejado no v2, use a opção
json.OmitEmptyWithLegacyDefinition(true)
.
3. Mudança no comportamento da opção de tag string
- Comportamento do v1: Aplica-se a campos de tipo numérico, booleano e string. Recodifica o valor dentro de uma string JSON (por exemplo,
int(42)
->"42"
). Não se aplica recursivamente a valores dentro de tipos compostos (slices, maps, etc.). - Comportamento do v2: Aplica-se apenas a tipos numéricos e recursivamente. Ou seja, números dentro de slices como
[]int
também são codificados como strings JSON. - Razão da mudança: O objetivo principal da opção
string
é representar números como strings para evitar a perda de precisão de inteiros de 64 bits. O comportamento do v1 era limitado e inconsistente. O v2 se concentra nesse uso principal e estende o comportamento recursivamente para torná-lo mais útil. - Opções relacionadas: O comportamento do v1 pode ser emulado com a opção
json.StringifyWithLegacySemantics(true)
.
4. Marshalling de slices e maps nil
- Comportamento do v1: Slices
nil
e mapsnil
são marshaled comonull
. - Comportamento do v2: Por padrão, slices
nil
são marshaled como[]
(array vazio) e mapsnil
como{}
(objeto vazio). - Razão da mudança:
nil
é um detalhe de implementação da linguagem Go, e expô-lo no formato JSON, que é independente da linguagem, não é desejável.[]
ou{}
para representar coleções vazias são expressões mais universais. - Opções relacionadas: No v2, é possível marshaling como
null
como no v1 através das opçõesjson.FormatNilSliceAsNull(true)
oujson.FormatNilMapAsNull(true)
.
5. Unmarshalling de arrays
- Comportamento do v1: Ao unmarshal para um array Go (
[N]T
), se o comprimento do array JSON for diferente do comprimento do array Go, nenhum erro é gerado. Se o comprimento for menor, o espaço restante é preenchido com valores zero; se for maior, o excesso é descartado. - Comportamento do v2: O comprimento do array JSON deve corresponder exatamente ao comprimento do array Go. Caso contrário, um erro é gerado.
- Razão da mudança: Arrays de tamanho fixo em Go frequentemente têm um significado importante em termos de seu comprimento. O comportamento do v1 pode levar a perdas silenciosas de dados. O v2 aumenta a precisão com regras mais rigorosas.
- Opções relacionadas: O comportamento do v1 pode ser emulado com a opção
json.UnmarshalArrayFromAnyLength(true)
.
6. Processamento de time.Duration
- Comportamento do v1:
time.Duration
é tratado internamente comoint64
e codificado como um número JSON em nanossegundos. - Comportamento do v2: É codificado como uma string JSON no formato
"1h2m3s"
usando o métodotime.Duration.String()
. - Razão da mudança: Nanossegundos numéricos são menos legíveis, e a representação de string padrão de
time.Duration
é mais útil. - Opções relacionadas: O comportamento do v1 pode ser usado através da opção de tag
json:",format:nano"
ou da opçãojson.FormatTimeWithLegacySemantics(true)
.
7. Tratamento de UTF-8 inválido
- Comportamento do v1: Durante o marshalling/unmarshalling, se houver bytes UTF-8 inválidos em uma string, eles são silenciosamente substituídos pelo caractere de substituição Unicode (
\uFFFD
). - Comportamento do v2: Por padrão, retorna um erro se encontrar UTF-8 inválido.
- Razão da mudança: Para evitar a corrupção silenciosa de dados e para aderir ao padrão JSON mais rigoroso (RFC 7493).
- Opções relacionadas: O comportamento do v1 pode ser emulado com a opção
jsontext.AllowInvalidUTF8(true)
.
8. Tratamento de nomes de membros de objeto duplicados
- Comportamento do v1: Permite que membros com o mesmo nome apareçam duplicados dentro de um objeto JSON. O valor da última ocorrência sobrescreve o anterior.
- Comportamento do v2: Por padrão, retorna um erro se houver nomes de membros duplicados.
- Razão da mudança: O padrão RFC 8259 não define o comportamento de nomes duplicados, o que pode levar a comportamentos diferentes entre implementações. Isso pode ser uma fonte de vulnerabilidades de segurança. O v2 rejeita isso explicitamente para aumentar a precisão e a segurança.
- Opções relacionadas: O comportamento do v1 pode ser emulado com a opção
jsontext.AllowDuplicateNames(true)
.
Diferenças de implementação e arquitetura
- v1: Depende muito de
decodeState
emdecode.go
e de uma máquina de estado (state machine) escrita manualmente emscanner.go
. Esta é uma estrutura monolítica onde a lógica de parsing e a análise semântica estão fortemente acopladas. - v2: A arquitetura é mais modular.
encoding/json/jsontext
: Fornece um tokenizador JSON (Decoder
) e um codificador (Encoder
) de baixo nível e alto desempenho. Este pacote se concentra apenas nos aspectos sintáticos do JSON.encoding/json/v2
: Lida com a conversão semântica entre tipos Go e valores JSON, com base emjsontext
.- Essa separação melhora a clareza do código e o desempenho, separando a análise sintática da análise semântica.
Novas APIs e recursos do v2
O v2 oferece recursos de controle altamente flexíveis através do sistema json.Options
.
json.Options
: Um conjunto de opções que modificam o comportamento de marshalling/unmarshalling.json.JoinOptions(...)
: Combina várias opções em uma.WithMarshalers
/WithUnmarshalers
: Um recurso poderoso que permite injetar lógica de serialização/desserialização para tipos específicos sem precisar implementar as interfacesMarshaler
/Unmarshaler
. Isso é particularmente útil ao lidar com tipos de pacotes externos.- Novas opções:
RejectUnknownMembers
,Deterministic(false)
,FormatNilSliceAsNull
, etc., que permitem uma variedade de controles de comportamento que não eram possíveis no v1.
Conclusão
O encoding/json
v2 é uma implementação moderna que, com base na experiência do v1, melhora significativamente a precisão, o desempenho e a flexibilidade. Embora o comportamento padrão tenha se tornado mais rigoroso, o sistema Options
refinado suporta completamente todos os comportamentos do v1, permitindo a introdução gradual dos benefícios do v2, mantendo a compatibilidade com o código existente.
- Para novos projetos, é recomendável usar o v2 por padrão.
- Projetos existentes podem continuar usando
jsonv1
, ou migrar parajsonv2
e adotar gradualmente o comportamento rigoroso do v2 através deDefaultOptionsV1()
.