O artigo do blog oficial do Go anuncia o lançamento de uma nova API experimental para JSON, presente nos pacotes encoding/json/v2 e encoding/json/jsontext, com o objetivo de abordar as limitações e problemas existentes no pacote encoding/json (denominado v1 neste contexto). Após quase 15 anos desde a introdução do suporte a JSON em Go, e com o JSON se tornando o formato de dados mais popular na internet, a equipe de desenvolvimento do Go reconheceu a necessidade de evoluir o pacote padrão para atender às demandas modernas e corrigir falhas identificadas ao longo do tempo.
Problemas Identificados no encoding/json (v1)
O artigo detalha uma série de problemas com o pacote encoding/json original, categorizados em falhas de comportamento, deficiências na API e limitações de desempenho.
Falhas de Comportamento
- Manipulação Imprecisa da Sintaxe JSON: O
encoding/jsoné permissivo com relação a sintaxes JSON inválidas.- Aceita UTF-8 inválido, enquanto o padrão mais recente (RFC 8259) exige UTF-8 válido. Isso pode levar a corrupção silenciosa de dados.
- Aceita objetos com nomes de membros duplicados, o que pode levar a comportamentos inesperados e até mesmo vulnerabilidades de segurança. A RFC 8259 não especifica como lidar com nomes duplicados, deixando a implementação a cargo do interpretador JSON. Isso pode resultar em diferentes interpretações do mesmo JSON por diferentes sistemas, um risco explorado em ataques como o CVE-2017-12635.
- Vazamento da “nulidade” de slices e maps:
encoding/jsonserializa slices e maps nulos comonullem JSON. Isso pode causar problemas de interoperabilidade com sistemas que não aceitamnullpara arrays ou objetos. A maioria dos usuários Go prefere que slices e maps nulos sejam serializados como arrays ou objetos vazios por padrão. - Unmarshaling Insensível ao Caso: A correspondência entre nomes de membros JSON e campos de structs Go é feita de forma case-insensitive. Isso é um comportamento surpreendente, pode ser uma vulnerabilidade de segurança e limita o desempenho.
- Chamadas Inconsistentes de Métodos: Métodos
MarshalJSONdefinidos em um receptor ponteiro são chamados de forma inconsistente peloencoding/jsondevido a detalhes de implementação. Embora seja considerado um bug, não pode ser corrigido devido à dependência de muitas aplicações no comportamento atual.
Deficiências na API
- Dificuldade em Unmarshaling de um
io.Reader: É difícil garantir que não haja “lixo” (trailing data) após um valor JSON válido ao deserializar de umio.Readerusandojson.NewDecoder(r).Decode(v). - Opções Limitadas: As opções definidas nos tipos
EncodereDecodernão podem ser usadas com as funçõesMarshaleUnmarshal. Da mesma forma, tipos que implementam as interfacesMarshalereUnmarshalernão podem usar as opções, criando uma inconsistência na aplicação de configurações. Por exemplo, a opçãoDecoder.DisallowUnknownFieldsperde o efeito ao chamar um métodoUnmarshalJSONpersonalizado. - Funções de Formatação Limitadas: As funções
Compact,IndenteHTMLEscapegravam em umbytes.Bufferem vez de uma interface mais flexível como[]byteouio.Writer, o que limita sua usabilidade.
Limitações de Desempenho
- MarshalJSON: A interface
MarshalJSONforça a alocação de um[]byteretornado. Além disso, oencoding/jsondeve verificar se o resultado é JSON válido e reformatá-lo para corresponder à indentação especificada. - UnmarshalJSON: A interface
UnmarshalJSONrequer um valor JSON completo (sem dados extras). Isso força oencoding/jsona analisar o valor JSON a ser deserializado por completo para determinar onde termina antes de chamarUnmarshalJSON. Depois, o métodoUnmarshalJSONem si deve analisar o valor JSON fornecido novamente. - Falta de Streaming: Embora os tipos
EncodereDecoderoperem em umio.Writerouio.Reader, eles armazenam em buffer o valor JSON inteiro na memória. O métodoDecoder.Tokenpara ler tokens individuais é pesado em alocação, e não há uma API correspondente para escrever tokens.
Além disso, se a implementação de um método MarshalJSON ou UnmarshalJSON chama recursivamente a função Marshal ou Unmarshal, o desempenho se torna quadrático.
A Necessidade de uma Nova Versão (v2)
O artigo explica que, embora a equipe de desenvolvimento tenha considerado corrigir o pacote encoding/json diretamente, as limitações da API existente e a promessa de compatibilidade do Go 1 tornaram isso impraticável. A introdução de uma nova versão principal (v2) permite quebrar a compatibilidade com a versão anterior e introduzir as mudanças necessárias para resolver os problemas identificados.
A alternativa de criar funções separadas, como MarshalV2 ou UnmarshalV2, foi considerada, mas descartada por ser equivalente a criar um namespace paralelo dentro do mesmo pacote. A solução encontrada foi criar o pacote encoding/json/v2, que oferece um novo namespace onde as mudanças podem ser feitas sem afetar a compatibilidade com o encoding/json original (v1).
Planejamento do encoding/json/v2
O planejamento para a nova versão do encoding/json levou anos. Em 2020, a impossibilidade de corrigir os problemas no pacote existente impulsionou Daniel Martí a elaborar um documento sobre como seria um pacote v2 hipotético. Joe Tsai, após trabalhar na API do Go para Protocol Buffers, percebeu que o pacote protojson precisava usar uma implementação JSON personalizada, pois o encoding/json não era capaz de aderir ao padrão JSON mais rigoroso exigido pela especificação do Protocol Buffer, nem de serializar JSON de forma eficiente em streaming.
Daniel e Joe uniram forças para criar um protótipo para o v2, com o código inicial sendo uma versão aprimorada da lógica de serialização JSON do módulo Go protobuf. Ao longo do tempo, outros (Roger Peppe, Chris Hines, Johan Brandhorst-Satzkorn e Damien Neil) se juntaram ao esforço, fornecendo revisão de design, revisão de código e testes de regressão.
O Novo Pacote encoding/json/v2
O artigo não entra em detalhes específicos sobre a nova API, mas implicitamente sugere que ela abordará os problemas mencionados acima:
- Sintaxe JSON Mais Estrita: A nova API deve ser mais rigorosa na validação da sintaxe JSON, rejeitando UTF-8 inválido e objetos com nomes de membros duplicados.
- Controle da “Nulidade”: A nova API provavelmente oferecerá opções para controlar como slices e maps nulos são serializados (como
nullou como arrays/objetos vazios). - Unmarshaling Sensível ao Caso: A nova API provavelmente usará case-sensitive por padrão.
- API Mais Consistente: A nova API deve ter uma API mais consistente, com opções que podem ser usadas com todas as funções de marshaling e unmarshaling, e a capacidade de passar opções para métodos
MarshalJSONeUnmarshalJSONpersonalizados. - Melhor Desempenho: A nova API pode oferecer suporte a streaming para reduzir o uso de memória e evitar a necessidade de analisar o JSON várias vezes.
O Pacote encoding/jsontext
O artigo também menciona o pacote encoding/jsontext, que provavelmente lida com a representação textual de JSON e pode oferecer funcionalidades adicionais relacionadas à manipulação de texto JSON. Detalhes específicos sobre este pacote não são fornecidos no texto.
Status Experimental e Próximos Passos
É crucial notar que os pacotes encoding/json/v2 e encoding/jsontext são experimentais e podem sofrer alterações na API no futuro. O objetivo é coletar feedback da comunidade e refinar a API antes de torná-la estável.
Para usar os pacotes experimentais, é necessário importá-los explicitamente:
import "encoding/json/v2"
import "encoding/json/jsontext"
A equipe de desenvolvimento incentiva os usuários a experimentar a nova API e fornecer feedback para ajudar a moldar o futuro do JSON em Go.
Em resumo, o lançamento dos pacotes encoding/json/v2 e encoding/jsontext representa um passo importante na evolução do suporte a JSON em Go, visando resolver problemas de longa data e fornecer uma API mais moderna, flexível e eficiente. A fase experimental é fundamental para garantir que a nova API atenda às necessidades da comunidade Go e se torne um padrão robusto para o futuro.
Artigo Original
Este e um resumo em português do artigo original publicado no blog oficial do Go.
Titulo original: A new experimental Go API for JSON
Leia o artigo completo em ingles no Go Blog
Autor original: Joe Tsai, Daniel Martí, Johan Brandhorst-Satzkorn, Roger Peppe, Chris Hines, and Damien Neil