DEV Community

John Nyingi
John Nyingi

Posted on • Edited on

JSONSerializer meets Source Generators

With the release of .Net 6.0, we saw the introduction of Source Generators in the JsonSerializer library.
So, what is a Source Generator? Well, it is a tool that analyzes code during build and generates code accordingly. It can only add code to the build process and CANNOT edit or remove code.

What are its Benefits?

Performance is one of the biggest gains for a Source Generated JSONSerializer. These performance gains come in the following ways.

1) No Reflection: Reflection is a well-known performance bottleneck. With Source Generators you can scan the Syntax tree get all the components and generate code (all in build time), which significantly reduces the performance penalty during runtime.

2) Native AOT: Applications such as Serverless Applications need fast (cold)start times. To create applications that support Native AOT we CANNOT use Reflection since the process of generating Native Code involves trimming the application, thus the application will fail to run.
Source Generators can emit all the code needed during build such that when we are trimming we get a complete working app. With Native AOT the gains are made in reduced application size and fast start times.

NOTE: In this example I am using .Net 8

How do you use it?

Let's define a class Person:

class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public float Height { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Next, let's define a Context class that JsonSerializer will use to generate code.

[JsonSerializable(typeof(Person))]
internal partial class SourceGeneratorContext : JsonSerializerContext
{

}

Enter fullscreen mode Exit fullscreen mode

You'll notice we have the attribute JsonSerializable, this attribute is used by the Source Generate to register the type. During compilation, the Source Generator reads the type passed on the attribute and fetches its syntax.

Now we can Serialize and Deserialize.

var person = new Person()
{
    Name = "Alex",
    Age = 24,
    Height = 6.2f
};

var jsonString = JsonSerializer.Serialize(person, SourceGeneratorContext.Default.Person);
Console.WriteLine(jsonString);

// Result
// {
//     "Name": "Alex",
//     "Age": 24,
//     "Height": 6.2
// }
Enter fullscreen mode Exit fullscreen mode

Deserializing:


string jsonStr = "{\n  \"Name\": \"Esther\",\n  \"Age\": 26,\n  \"Height\": 5.9\n}";
var personObj = JsonSerializer.Deserialize<Person>(jsonStr, SourceGeneratorContext.Default.Person);

Enter fullscreen mode Exit fullscreen mode

You'll notice in both the Serializer and Deserializer that we pass in SourceGeneratorContext.Default.Person This is a Source generated Option for our Person class.

You can also see from the example we are not using camelcase for our Json We can add this option using the JsonSourceGenerationOptions attribute.
We add it to our Context Class

[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
[JsonSerializable(typeof(Person))]
internal partial class SourceGeneratorContext : JsonSerializerContext
{

}

Enter fullscreen mode Exit fullscreen mode

we also changed our deserializer implementation to match the camelcase


string jsonStr = "{\n  \"name\": \"Esther\",\n  \"age\": 26,\n  \"height\": 5.9\n}";
var personObj = JsonSerializer.Deserialize<Person>(jsonStr, SourceGeneratorContext.Default.Person);

Enter fullscreen mode Exit fullscreen mode

For a more granular control over the options, you can use the JsonSerializerOptions class to define your options.

var serializerOptions = new JsonSerializerOptions
{   
    TypeInfoResolver = SourceGeneratorContext.Default,
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals,
};
Enter fullscreen mode Exit fullscreen mode

We remove the JsonSourceGenerationOptions from the SourceGeneratorContext

[JsonSerializable(typeof(Person))]
internal partial class SourceGeneratorContext : JsonSerializerContext
{

}
Enter fullscreen mode Exit fullscreen mode

and now we can implement Serialization and Deserialization like so

var jsonString = JsonSerializer.Serialize(person, serializerOptions);

// Result
// {
//     "name": "Alex",
//     "age": 24,
//     "height": 6.2
// }
string jsonStr = "{\n  \"name\": \"Esther\",\n  \"age\": 26,\n  \"height\": 5.9\n}";
var personObj = JsonSerializer.Deserialize<Person>(jsonStr, serializerOptions);

Enter fullscreen mode Exit fullscreen mode

In .Net 8 you can completely turn off the reflection-based serializer option by adding JsonSerializerIsReflectionEnabledByDefault to your .csproj file like so.

<PropertyGroup>
 <JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault>
</PropertyGroup>
Enter fullscreen mode Exit fullscreen mode

Conclusion

With the introduction of Source Generators, its adoption across the .Net runtime has proven to be great for performance gains and overall user experience.

JsonSerializer a defacto package for working with Json has improved significantly as a result of supporting Source Generators.

Give it a try!!

Top comments (0)