Neemle.XJson.Generator 1.0.2

dotnet add package Neemle.XJson.Generator --version 1.0.2
                    
NuGet\Install-Package Neemle.XJson.Generator -Version 1.0.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Neemle.XJson.Generator" Version="1.0.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Neemle.XJson.Generator" Version="1.0.2" />
                    
Directory.Packages.props
<PackageReference Include="Neemle.XJson.Generator" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Neemle.XJson.Generator --version 1.0.2
                    
#r "nuget: Neemle.XJson.Generator, 1.0.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Neemle.XJson.Generator@1.0.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Neemle.XJson.Generator&version=1.0.2
                    
Install as a Cake Addin
#tool nuget:?package=Neemle.XJson.Generator&version=1.0.2
                    
Install as a Cake Tool

XJson — reflection‑free JSON for System.Text.Json

XJson is an incremental source generator that emits reflection‑free System.Text.Json converters for your models. It’s designed to be AOT/NativeAOT and trimming friendly: no runtime reflection, no JsonSerializerContext required, and no dynamic lookup of converters. You annotate your types, the generator produces per‑type converters, and a small static helper provides a simple API.

Features

  • Reflection‑free, AOT/NativeAOT friendly JSON serialization and deserialization
  • Works under aggressive trimming (no runtime reflection or dynamic codegen)
  • Simple helper API: Json.Encode<T>, Json.Decode<T>, and Json.Validate<T>
  • Respects [JsonPropertyName] on properties (attribute name wins)
  • Configurable via:
    • JsonSerializerOptions (runtime options)
    • JsonSourceGenerationOptionsAttribute (as a convenient options bag)
  • CamelCase by default and “ignore nulls when writing”

Under the hood

XJson builds on System.Text.Json primitives — it generates code that uses Utf8JsonWriter and Utf8JsonReader directly. There is no reliance on JsonSerializer.Serialize/Deserialize, no JsonSerializerContext, and no runtime reflection. This keeps the pipeline small, fast, and friendly to trimming and NativeAOT.

What’s generated

For each type annotated with [Neemle.XJson.Abstractions.XJson], the generator emits:

  • An internal JsonConverter<T> with hand‑written (generated) Utf8JsonWriter/Utf8JsonReader logic
  • A static helper in Neemle.XJson.Generated namespace:
    • public static class Json
      • string Encode<T>(T value)
      • string Encode<T>(T value, JsonSerializerOptions? options)
      • string Encode<T>(T value, JsonSourceGenerationOptionsAttribute? genOptions)
      • T Decode<T>(string json)
      • T Decode<T>(string json, JsonSerializerOptions? options)
      • T Decode<T>(string json, JsonSourceGenerationOptionsAttribute? genOptions)
      • bool Validate<T>(string json, out T? value, out string? error)
      • bool Validate<T>(string json, out T? value, out string? error, JsonSerializerOptions? options)
      • bool Validate<T>(string json, out T? value, out string? error, JsonSourceGenerationOptionsAttribute? genOptions)

Design-time experience

  • When no [XJson] types are present in the current design-time compilation, the generator emits a minimal throw-only stub for Neemle.XJson.Generated.Json. This keeps IntelliSense happy (no missing-type errors) while leaving runtime builds unchanged and reflection-free.

Installation

Add a project reference to the Abstractions and add the Generator as an analyzer to the project(s) where you define your models:

<ItemGroup>
  <ProjectReference Include="..\Abstractions\Abstractions.csproj" />
  <ProjectReference Include="..\XJson.Generator\XJson.Generator.csproj"
                    OutputItemType="Analyzer"
                    ReferenceOutputAssembly="false" />
</ItemGroup>


<PropertyGroup>
  <CompilerGeneratedFilesOutputPath>obj/Generated</CompilerGeneratedFilesOutputPath>
  <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
  <Nullable>enable</Nullable>
  <ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

Quick start

  1. Annotate your models with [XJson] (and optionally standard STJ attributes like [JsonPropertyName]):
using System.Text.Json.Serialization;
using Neemle.XJson.Abstractions;

[XJson]
public class Person
{
    [JsonPropertyName("a")] // attribute name wins over any naming policy
    public string Name { get; set; } = string.Empty;
    public string Surname { get; set; } = string.Empty;
    public int Age { get; set; }
    public Address Address { get; set; } = new();
}

[XJson]
public class Address
{
    public string Street { get; set; } = string.Empty;
    public string City { get; set; } = string.Empty;
}
  1. Serialize / deserialize using the generated helper:
using System.Text.Json;
using Neemle.XJson.Generated; // exposes the static Json helper

var person = new Person { Name = "Ada", Surname = "Lovelace", Age = 36, Address = new Address { Street = "12 St James's Square", City = "London" } };

// Defaults: camelCase, ignore nulls, indented
string json = Json.Encode(person);
var copy = Json.Decode<Person>(json);

// Validate without throwing
if (Json.Validate<Person>(json, out var value, out var error))
{
    Console.WriteLine($"OK: {value!.Surname}");
}
else
{
    Console.WriteLine($"Invalid: {error}");
}

Configuring options

You can pass either JsonSerializerOptions or JsonSourceGenerationOptionsAttribute to control naming and null handling.

  • With JsonSerializerOptions (runtime options):
var opts = new JsonSerializerOptions
{
    WriteIndented = false,
    PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
};

string json = Json.Encode(person, opts);
var p = Json.Decode<Person>(json, opts);
  • With JsonSourceGenerationOptionsAttribute (as an options bag):
using System.Text.Json.Serialization;

var sg = new JsonSourceGenerationOptionsAttribute
{
    PropertyNamingPolicy = JsonKnownNamingPolicy.KebabCaseLower,
    WriteIndented = false,
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
};

string json = Json.Encode(person, sg);
var p = Json.Decode<Person>(json, sg);

Property naming precedence

  • [JsonPropertyName("...")] on a property always wins for both write and read.
  • If no attribute is present, the configured naming policy is applied (e.g., camelCase by default).
  • If neither attribute nor policy is provided, the CLR property name is used.

Supported naming policies from JsonKnownNamingPolicy map to JsonNamingPolicy when available on your target TFM:

  • Always: Unspecified → null, CamelCase → JsonNamingPolicy.CamelCase
  • .NET 8+: SnakeCaseLower, SnakeCaseUpper, KebabCaseLower, KebabCaseUpper

Supported types (v1)

  • Primitives: string, bool, int, long and their nullable variants
  • Nested models: other [XJson] types (including nullable nested)

Current limitations (throw NotSupportedException)

  • double/float/decimal, Guid, DateTime/DateTimeOffset
  • Enums, arrays/lists/dictionaries, records, required members, polymorphism
  • Custom per‑property converters

Null handling

  • When DefaultIgnoreCondition = WhenWritingNull, reference and Nullable<T> properties with null are omitted on write.
  • Otherwise, they are emitted as "prop": null.

AOT and trimming notes

  • No JsonSerializer.Serialize/Deserialize calls are used in generated code paths.
  • No JsonSerializerOptions.GetConverter(Type) or runtime reflection is used.
  • Converters are invoked directly, making the approach compatible with trimming and NativeAOT.

Testing

The repository includes a test project XJson.Tests (xUnit + FluentAssertions).

  • How to run:

    • dotnet test (or run the XJson.Tests project directly). The tests target net10.0 by default.
  • What’s covered:

    • Encode/Decode round-trips for [XJson] types with nested objects
    • [JsonPropertyName] taking precedence over naming policies
    • Naming policies via both JsonSerializerOptions and JsonSourceGenerationOptionsAttribute
    • Validate<T> success/failure behavior with error messaging
    • Null handling differences between WhenWritingNull and Never
    • Errors for unsupported property types and for non‑annotated types

FAQ

Q: Do I still need JsonSerializerContext or STJ’s source generator? A: No. XJson emits its own converters and a thin helper. You can still use STJ attributes like [JsonPropertyName] for naming.

Q: Can I mix XJson models with regular STJ serialization? A: Yes, but XJson’s generated helper only handles [XJson] types. For others, use your regular STJ pipeline.

Q: How do I extend supported types? A: Extend the generator to handle more primitives / collections / enums or file an issue/PR.

License

MIT — see LICENSE.

There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

  • net10.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.2 259 11/24/2025
1.0.1 289 11/22/2025
1.0.0 433 11/20/2025