Bien sérialiser ses objets en .NET grâce aux Source Generators
Publié le 13/02/2025
Par  Christophe MOMMER

La sérialisation est une étape indispensable lorsque l'on souhaite exposer des objets en programmation web. XML, Json et Protobuf sont les trois formats qui règnent en maître, et le standard de facto de nos jours est le JSON. De ce fait, ce billet va s'intéresser à ce format.

Au fur et à mesure des évolutions de .NET, Microsoft s'est rendu compte que se reposer sur un package communautaire n'était probablement pas la meilleure idée qui soit à cause de problèmes de compatibilité de version. Ainsi, on a pu voir l'intégration automatique de Newtonsoft.Json s'effacer pour laisser la place au serializer du framework lui-même. Bien que certains cas d'usages très particuliers nécessite toujours l'usage du package communautaire, on peut aujourd'hui faire énormément de choses avec le serializer du framework.

Cependant, appeller simplement la méthode JsonSerializer.Serialize (et son pendant inverse JsonSerializer.Deserialize) n'est pas le plus optimisé. Le plus rapide a faire, probablement. Mais Microsoft nous a gratifié de Source Generators embarqués dans le framework pour rendre ces opérations encore plus rapide.

Tout d'abord, petite définition d'un source generator. Le concept n'est pas nouveau, mais il fait maintenant pleinement partie du framework et du compilateur. Il s'agit simplement d'un bout de code qui va analyser les éléments de votre code et, lors du processus de compilation, faire l'émission de code complémentaire au vôtre, qui fera partie du binaire final. En bref : le compilateur va compléter votre code.

Prenons un exemple simple : si je souhaite sérializer une instance de la classe Person qui contient un nom et un prénom, le plus simple serait de faire une sortie de texte brute où j'écris par moi-même les accolades ouvrantes, les guillemets, les étiquettes et les valeurs. Et bien, c'est exactement ce que le source generator du framework vous propose !

Voici le code "standard" :

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

var person = new Person { Name = "Christophe", LastName = "Mommer" };
var json = JsonSerializer.Serialize(person);

Ce que va faire le serializer ici, c'est utiliser le processus coûteux de réfléxion (introspection des informations du type à l'exécution) pour savoir qu'est ce qui doit être sérialisé et comment. Cette opération coûteuse, bien qu'améliorée dans les dernières versions de .NET, se produira lors de l'appel à la méthode Serialize.

On peut donner un coup de main au processus et générant le code en avance de phase, comme ceci :

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

La déclaration de cette classe est intéressante :

  • Tout d'abord, elle hérite de JsonSerializerContext, la classe qui permet de déterminer les instructions basiques pour faire l'output de la sérialisation Json (comme décrit ci-dessus, en faisant tout manuellement)
  • Bien qu'il soit tout à fait possible d'écrire le code manuellement, on va demander au compilateur de faire l'effort de compléter le code la classe avant de compiler l'assembly, d'où la classe est marquée comme partielle, afin que le compilateur puisse créer "l'autre part" de la classe
  • Finalement, l'ajout de l'attribute JsonSerializable indique justement au source generator que c'est sur cette classe là qu'il faut compléter le code. En bref, le source generator va scanner votre code pour trouver toutes les classes marquées comme telles

Une fois ceci fait, les IDEs et le compilateur sont géneralement assez réactif, et on peut trouver le code automatiquement généré dans les sources du projet.

Il conviendra, pour finir, de changer l'appel à la méthode de sérialisation pour utiliser le code source généré plutôt que la réfléxion :

var json = JsonSerializer.Serialize(person, PersonSerializationContext.Default.Person);

Note : il est possible de spécifier ce contexte de sérialisation dans la classe JsonSerializerOptions si nécessaire, tout comme il est possible de personnaliser le code généré en utilisant des attributs complémentaires.

Ceci vous offrira la plus haute performance possible de la sérialisation et, cerise sur le gâteau, le code est compatible AOT