JSONSerializer meets Source Generators
Tags: beginners, csharp, dotnet
Published: 2023-12-01 03:55:40

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](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)

### How do you use it?
Let's define a class Person:

```c#
class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public float Height { get; set; }
}
```
Next, let's define a Context class that `JsonSerializer` will use to generate code.

```c#
[JsonSerializable(typeof(Person))]
internal partial class SourceGeneratorContext : JsonSerializerContext
{
    
}

```
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.

```c#
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
// }
```
Deserializing: 

```c#

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

```

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

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

```
we also changed our deserializer implementation to match the camelcase

```c#

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

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

```c#
var serializerOptions = new JsonSerializerOptions
{   
    TypeInfoResolver = SourceGeneratorContext.Default,
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals,
};
```
We remove the `JsonSourceGenerationOptions` from the `SourceGeneratorContext`

```c#
[JsonSerializable(typeof(Person))]
internal partial class SourceGeneratorContext : JsonSerializerContext
{
    
}
```

and now we can implement Serialization and Deserialization like so

```c#
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);

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

```xml
<PropertyGroup>
 <JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault>
</PropertyGroup>
```

### 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!!