Ir para o conteúdo

No mundo das aplicações distribuídas, a integração entre microsserviços diferentes torna-se cada vez mais comum no dia a dia do desenvolvimento de software. O formato JSON (JavaScript Object Notation) é o padrão mais utilizado para a troca de informações entre microsserviços, o que exige a serialização e desserialização das informações trocadas em formato JSON. Em C#, há duas bibliotecas amplamente utilizadas para lidar com JSON: o Newtonsoft.Json, que é bastante robusta, e o System.Text.Json, que é uma alternativa mais recente ao Newtonsoft.Json e oferece uma performance mais leve. Apesar de haver uma nova opção disponível, o Newtonsoft.Json ainda é muito popular entre muitos desenvolvedores .NET e é, de longe, o pacote mais baixado no Nuget.org, conforme pode ser visto na Figura 1.

Figura 1 – nuget.org estatísticas de downloads dos pacotes

Enquanto o Newtonsft.Json é mantido por terceiros, o System.Text.Json é mantido pelo time de desenvolvimento do .NET e pode ser usada através do namespace System.Text.Json. Estando ela disponível nativamente a partir do .NET Core 3.1 e suas versões posteriores. Sendo ela também compatível com o .NET Standard 2.0, .NET Framework 4.7.2, .NET Core 2.0, 2.1 e 2.2, através da instalação do pacote System.Text.Json via Nuget.

Mas e o quão performático é o System.Text.Json em relação ao Newtonsoft.Json?

Essa pergunta pode ser respondida com a criação de um Benchmark que compare as duas bibliotecas para serialização e desserialização de JSON.

A biblioteca BenchmarkDotNet irá nos auxiliar nesse comparativo, sendo ela amplamente utilizada pelos times de desenvolvimento do próprio .NET em diversos projetos como: ASP.NET Core, Entity Framework Core, além também de ser utilizada pela equipe do Dapper, Serilog, Autofac e MediatR. Inclusive, ela foi usada no artigo Teste de performance de filtros globais para tratamento de exception em projetos feitos em NET Core. 

A ideia será executar uma aplicação do tipo console que execute a serialização usando o Newtonsoft.Json e System.Text.Json de uma lista de produtos.

A versão do .NET escolhida é a 7. Será necessário instalar dois pacotes, o BenchmarkDotNet e o Newtonsoft.Json nas versões mais recentes.

Para fazer a comparação entre as duas abordagens, vamos criar uma classe chamada JsonComparisons. Nela vamos ter dois campos somente leitura, uma para configurar a serialização padrão como Web do System.Text.Json e outra que irá representar a lista de produtos. E também teremos uma propriedade para definir o tamanho da lista de produtos a ser usado para comparação nos Benchmark. E por último, teremos dois métodos que servirão para executar a serialização em cada uma das abordagens, ficando da seguinte forma:

[MemoryDiagnoser]
[RankColumn]
public class JsonComparisons
{
    private readonly JsonSerializerOptions _jsonOptions;
    private readonly List<Produto> _produtos;

    [Params(10, 100, 1_000, 10_000, 100_000, 1_000_000)]
    public int TamanhoLista { get; set; }

    public JsonComparisons()
    {
        _jsonOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web);

        _produtos = ConstruirProdutos(TamanhoLista);
    }

    private List<Produto> ConstruirProdutos(int count)
    {
        return Enumerable.Range(1, count)
            .Select(i => new Produto()
            {
                Id = i,
                Nome =

quot;Nome {i}",
Categoria =

quot;Categoria {i}",
Descricao =

quot;Descrição {i}",
Preco = i
})
.ToList();
}

[Benchmark]
public string Newtonsoft() => JsonConvert.SerializeObject(_produtos);

[Benchmark]
public string SystemTextJson() => System.Text.Json.JsonSerializer.Serialize(_produtos, _jsonOptions);
}

Por fim, é alterar o método Main da classe Program para executar o benchmark para a classe de comparação das abordagens de serialização de JSON, ficando da seguinte forma:

public class Program
{
    static void Main(string[] args)
    {
        BenchmarkRunner.Run<JsonComparisons>();
    }        
}

Para executar o Benchmark, basta abrir o terminal na pasta onde está salvo o csproj do projeto criado e executar usando o CLI (command-line interface) do .NET da seguinte maneira.

dotnet run -c Release

Ao final da execução, teremos uma tabela com os resultados conforme Figura 2.

Figura 2 – resultado do Benchmark

Nela temos as seguintes informações:

  • Mean: Média aritmética de todas as medições
  • Error: Metade do intervalo de confiança de 99,9%
  • StdDev: Desvio padrão de todas as medições
  • Rank: Posição relativa da média do benchmark atual entre todos os benchmarks
  • Gen0: A geração 0 do GC coleta por 1.000 operações
  • Gen1: A geração 1 do GC coleta por 1.000 operações
  • Allocated: Memória alocada por operação única (somente gerenciada, inclusive, 1KB = 1024B)
  • 1 us: 1 Microsecond (0.000001 sec)

Com base nos resultados retornados, podemos dizer que o System.Text.Json é mais rápido do que o Newtonsoft.Json. Para provar isso, podemos calcular a diferença percentual de desempenho entre os dois métodos usando a fórmula ((Newtonsoft – SystemTextJson) / Newtonsoft) * 100, onde teremos os seguintes resultados para todos tamanhos de lista de produtos usados no Benchmark:

  • TamanhoLista = 10: ((376.3 – 196.5) / 376.3) * 100 = 47,28%
  • TamanhoLista = 100: ((402.7 – 196.2) / 402.7) * 100 = 51,19%
  • TamanhoLista = 1000: ((371.3 – 203.5) / 371.3) * 100 = 45,69%
  • TamanhoLista = 10000: ((379.5 – 189.7) / 379.5) * 100 = 49,97%
  • TamanhoLista = 100000: ((372.7 – 196.9) / 372.7) * 100 = 47,13%
  • TamanhoLista = 1000000: ((424.5 – 199.4) / 424.5) * 100 = 53,03%

Portanto, podemos concluir que, em média, o SystemTextJson é cerca de 48,5% mais rápido do que o Newtonsoft.Json na desserialização de objetos.

Tem como ser melhor ainda?

Sim, e a abordagem consiste em utilizar uma sobrecarga do Serialize do JsonSerializer, que nos permite passar o Metadata do tipo a ser convertido. Para isso, é necessário criar uma classe parcial de geração de código que herde do JsonSerializerContext e seja decorada com os atributos JsonSourceGenerationOptions e JsonSerializable, como mostrado no código abaixo:

[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
[JsonSerializable(typeof(List<Produto>))]
public partial class ProdutoGenerationContext : JsonSerializerContext { }

Ao final, adicione método de Benchmark na classe JsonComparsion que irá testar a abordagem de geração de código a ser usado na serialização com o System.Text.Json.

[Benchmark]
public string SourceGenerator() => System.Text.Json.JsonSerializer.Serialize(_produtos, ProdutoGenerationContext.Default.ListProduto);

Agora basta executar novamente o projeto de benchmark para coletarmos os resultados de performance a respeito das três abordagens. E conforme a Figura 3 podemos ver que usar uma abordagem de geração de código é ainda mais performático.

Figura 3 – resultado Benchmark das 3 abordagens

Com esse Benchmark, podemos concluir que o System.Text.Json é mais performático, principalmente quando usado com a estratégia de geração de código.

Em relação ao método Newtonsoft:

  • Diferença de desempenho: 369.3 – 135.1 = 234.2
  • Diferença percentual: (234.2 / 135.1) x 100% = 173.35%

Em relação ao método SystemTextJson:

  • Diferença de desempenho: 182.7 – 135.1 = 47.6
  • Diferença percentual: (47.6 / 135.1) x 100% = 35.23%

Portanto, em relação ao método Newtonsoft, o SourceGenerator é cerca de 173.35% mais rápido em média, e em relação ao método SystemTextJson, o SourceGenerator é cerca de 35.23% mais rápido em média.


Conclusão

Com as comparações feitas, é notório que a performance do SystemTextJson é superior ao NewtonSoft.Json, especialmente quando usamos uma abordagem de geração de código, que chegou a ser 3 vezes mais performático que o Newtonsoft.Json.

Mas é importante levar em consideração as compatibilidades de funcionalidades entre o System.Text.Json e o Newtonsoft.Json antes de migrar. Você pode ver uma lista completa entre as diferenças na própria documentação do .NET através do link https://learn.microsoft.com/pt-br/dotnet/standard/serialization/system-text-json/migrate-from-newtonsoft?pivots=dotnet-7-0#table-of-differences-between-newtonsoftjson-and-systemtextjson

O código fonte completo para esse benchmark pode ser baixado no github (https://github.com/pablotdv/JsonBenchmark).

Outras publicações