Codeblox Blog

Como e se vale a pena usar JsonSourceGenerator

Emerson
22/11/2022

Hello everyone, dia 8 de novembro de 2022 tivemos o lançamento oficial do dotnet 7 e com ele muitos recursos, e hoje vamos ver um pouco sobre JsonSourceGenerator e compará-lo ao modo clássico de serialização.

1. Criando o contexto e gerando código

Vamos utilizar um conjunto de atributos e classes para nos ajudar a garantir que o código gerado seja exatamente como desejamos ou chegue perto disso.

Criaremos uma classe para ser a base de configurações e implementações, chamarei de AppJsonSerializerContext e ela deve ser partial e precisamos herdar da seguinte classe JsonSerializerContext. Com isso nós vamos nos deparar com alguns erros esperados sobre implementação de membros abstratos por conta da herança adicionada

Screenshot 2022-11-20 150811

e para resolver esses erros vamos implementar esses membros abstratos? Não, o JsonSourceGenerator fara isso por nós, basicamente vamos usar um atributo que será olhado pelo JsonSourceGenerator.

Vamos decorar a classe com o seguinte atributo JsonSerializable e vamos passar para ele um tipo para o qual queremos realizar o mapeamento via JsonSourceGenerator e com isso teremos um código semelhante a esse.

// Classe de exemplo para ser mapeada pelo JsonSourceGenerator
public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; private set; }
}


[JsonSerializable(typeof(Person))]
public partial class AppJsonSerializerContext : JsonSerializerContext 
{ 

}

Se tentarmos navegar para a class AppJsonSerializerContext veremos que vamos ter algumas implementações criadas pelo JsonSourceGenerator e a quantidade de implementações tende a variar conforme os tipos presente na classe Person. Nela podemos observar que estamos trabalhando com o tipo string e o int ou Int32, mas vale para os tipos complexos que podemos por ventura usar ali.

Recomendo aqui, que você navegue por essas implementações pois certamente tem muitas coisas que você pode aprender ali.

Com isso já estamos prontos para usar nosso Contexto.

2. Usando o código gerado

O consumo desse mapeamento gerado pelo JsonSourceGenerator é extremamente simples. Basicamente teremos que usar qualquer método de JsonSerializer que

Aqui vou demonstrar alguns exemplos de serialização

Usando JsonTypeInfo<T>:

jsonString = JsonSerializer.Serialize(person!, AppJsonSerializerContext.Default.Person);

</br>

Usando JsonSerializerContext:

jsonString = JsonSerializer.Serialize(person, typeof(Person), AppJsonSerializerContext.Default);

</br>

Usando JsonSerializerOptions:

sourceGenOptions = new JsonSerializerOptions
{
    TypeInfoResolver = AppJsonSerializerContext.Default
};

jsonString = JsonSerializer.Serialize(person, typeof(Person), sourceGenOptions);

Exemplos de desserialização

Usando JsonTypeInfo<T>:

var person = JsonSerializer.Deserialize(jsonString, AppJsonSerializerContext.Default.Person);

</br>

Usando JsonSerializerContext:

var person = JsonSerializer.Deserialize(jsonString, typeof(Person), AppJsonSerializerContext.Default)
    as WeatherForecast;

</br>

Usando JsonSerializerOptions:

var sourceGenOptions = new JsonSerializerOptions
{
    TypeInfoResolver = AppJsonSerializerContext.Default
};
var person = JsonSerializer.Deserialize(jsonString, typeof(Person), sourceGenOptions)
    as WeatherForecast;

3. Performance e pontos a se considerar

Vale ressaltar que o verdadeiro ganho de usar JsonSourceGenerator é a serialização e não a desserialização. Vamos ver aqui um teste de benchmarking onde podemos ver mais claramente que quando se trata de serialização o JsonSourceGenerator tem uma vantagem de 40% em relação a serialização clássica, já enquanto a desserialização o ganho quase não existe e as vezes tende a ser entre 1% e 2% mais lento que a desserialização clássica.

Screenshot 2022-11-15 222423

3.1 Vou tentar listar aqui alguns pontos validos que podem lhe ajudar a considerar usar ou não estes recursos

Caso seja usado será lançado a seguinte exceção

InvalidOperationException("The member 'Person.FirstName' has been annotated with the JsonIncludeAttribute but is not visible to the source generator.")

É valido mencionar que em casos que você precisa apenas de serialização você pode indicar no contexto para que o gerador gere apenas código para esse fim com o seguinte atributo.

[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Serialization)]

4. É possível usar esses recursos com o ASP.NET?

Sim, é simples, apenas precisamos registrar nosso contexto criado anteriormente e nossa aplicações já estaria pronta para isso.

services.AddControllers().AddJsonOptions(options => options.JsonSerializerOptions.AddContext<AppJsonSerializerContext>());

5. Deixo aqui um exemplo completo de uso.

Modelos

public class Person
{
    public Name Name { get; set; }
    public int Age { get; set; }
    public DateTime RegisteredIn { get; set; }
    public IEnumerable<Role> Roles { get; set; }
}

public class Name
{    
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

public class Role
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Tag { get; set; }
}

Contexto

[JsonSourceGenerationOptions(
    GenerationMode = JsonSourceGenerationMode.Default, // Serialize or Deserialize
    IgnoreReadOnlyFields = true,
    PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
    IgnoreReadOnlyProperties = true,
    IncludeFields = false,
    WriteIndented = true)]
[JsonSerializable(typeof(IEnumerable<Person>))]
public partial class AppJsonSerializerContext : JsonSerializerContext 
{ 

}

Consumindo

var roles = new List<Role>
{
    new Role { Id = Random.Shared.Next(), Name = "Teste1", Tag = "teste-1" },
    new Role { Id = Random.Shared.Next(), Name = "Teste2", Tag = "teste-2" },
    new Role { Id = Random.Shared.Next(), Name = "Teste3", Tag = "teste-3" }
};

var people = new List<Person>
{
    new Person
    {
        Age = Random.Shared.Next(),
        Name = new Name { FirstName = "Emerson", LastName = "Trindade" },
        RegisteredIn = DateTime.Now,
        Roles = roles
    },
    new Person
    {
        Age = Random.Shared.Next(),
        Name = new Name { FirstName = "Bento", LastName = "Trindade" },
        RegisteredIn = DateTime.Now,
        Roles = roles
    }
};

var jsonString = JsonSerializer.Serialize(people, AppJsonSerializerContext.Default.IEnumerablePerson);
var peopleDeserialized = JsonSerializer.Deserialize(jsonString, AppJsonSerializerContext.Default.IEnumerablePerson);
[
  {
    "name": {
      "firstName": "Emerson",
      "lastName": "Trindade"
    },
    "age": 1859346082,
    "registeredIn": "2022-11-20T17:26:24.0111108-03:00",
    "roles": [
      {
        "id": 961491365,
        "name": "Teste1",
        "tag": "teste-1"
      },
      {
        "id": 755995041,
        "name": "Teste2",
        "tag": "teste-2"
      },
      {
        "id": 2105727467,
        "name": "Teste3",
        "tag": "teste-3"
      }
    ]
  },
  ...
]

JsonSourceGenerator demonstra grande potencial, mas na minha humilde visão ainda precisa ser um pouco mais customizável para usar de forma mais generalizada, mas em casos específicos tem um ganho absurdo.

No mais obrigado por ter lido o artigo e eu ficaria muito grato pelo seu feedback, até a próxima.

csharp
dotnet
generators
roslyn


Emerson Trindade
Emerson Trindade
Desenvolvedor fullstack .Net/Angular/Azure/GitHub