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.
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
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.
O consumo desse mapeamento gerado pelo JsonSourceGenerator
é extremamente simples. Basicamente teremos que usar qualquer método de JsonSerializer
que
JsonTypeInfo<T>
ouJsonSerializerContext
ouJsonSerializerOptions
e você definiu sua propriedade JsonSerializerOptions.TypeInfoResolver como a Default do tipo de contexto (somente .NET 7 e posterior).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);
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;
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.
setter
privados não vão ser mapeados para desserialização mesmo usando [JsonInclude].
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.")
Para o modo clássico conseguimos implementar facilmente modificadores onde podemos manipular os tipos coisa que não será possível aqui com o JsonSourceGenerator
.
Se você considera usar AOT
e terá que usar serialização e desserialização será muito provável que você precise usar JsonSourceGenerator
pois o AOT
tem limitações quanto a reflexão que é usado pelo modo clássico de serialização e desserialização.
Casos com serialização em massa, onde sabemos que vamos constantemente serializar lista talvez com centenas ou milhares de itens vale a pena considerar usar JsonSourceGenerator
.
Se você considera usar a nova keyword required
do C# 11 tenha em mente que ela não tem suporte no JsonSourceGenerator
caso tente usar você tera um erro de compilação.
É 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)]
[JsonSourceGenerationOptions]
oferece, sugiro a você ver as propriedades que o atributo expõe.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>());
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.