JsonStreaming 1.0.0

dotnet add package JsonStreaming --version 1.0.0
                    
NuGet\Install-Package JsonStreaming -Version 1.0.0
                    
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="JsonStreaming" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="JsonStreaming" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="JsonStreaming" />
                    
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 JsonStreaming --version 1.0.0
                    
#r "nuget: JsonStreaming, 1.0.0"
                    
#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 JsonStreaming@1.0.0
                    
#: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=JsonStreaming&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=JsonStreaming&version=1.0.0
                    
Install as a Cake Tool

JsonStreaming

Streaming JSON array deserialization for ASP.NET Core helper using System.IO.Pipelines. Designed with Native AOT compatibility in mind.

NuGet License: MIT

Features

Installation

dotnet add package JsonStreaming

Quick Start

JsonStreaming supports two usage modes:

  1. With Source Generation (recommended for Native AOT and best performance)
  2. Without Source Generation (simpler setup, uses reflection-based deserialization)
1. Define your model with Source Generation
public record MyDto(int Id, string Name, decimal Value);

[JsonSourceGenerationOptions(PropertyNameCaseInsensitive = true)]
[JsonSerializable(typeof(MyDto))]
public partial class MyJsonContext : JsonSerializerContext
{
}
2. Register in DI (Choose One Method)

Add a single type

builder.Services.AddJsonStreamType(MyJsonContext.Default.MyDto);

Add multiple types using a Fluent API

builder.Services.AddJsonStreamTypes()
    .Add(MyJsonContext.Default.MyDto)
    .Add(MyJsonContext.Default.OtherDto)
    .Build();

Add to the registry directly

builder.Services.AddJsonStreamTypeInfo(registry =>
{
    registry.RegisterTypeInfo(MyJsonContext.Default.MyDto);
});

Auto-Discovery (Uses reflection)

builder.Services.AddJsonStreamContext<MyJsonContext>();
3. Use in Minimal API Endpoints
app.MapPost("/process", async (JsonStream<MyDto> items) =>
{
    await foreach (var item in items)
    {
        // Process each item as it arrives from the request
        Console.WriteLine($"Processing: {item.Name}");
    }
    return Results.Ok();
});

Option B: Without Source Generation (Simplest setup)

If you don't need Native AOT support, you can use JsonStreaming without source generation. The library will automatically fall back to reflection-based deserialization.

Note: JsonStream<T> automatically uses JsonSerializerDefaults.Web when no JsonSerializerOptions are registered, which provides:

  • ✅ Case-insensitive property matching
  • ✅ camelCase property names by default
  • ✅ Works with standard web APIs out of the box
1. Define your model (No Source Generation required)
public record MyDto(int Id, string Name, decimal Value);
2. Optional: Configure JsonSerializerOptions
// Only register custom JsonSerializerOptions if you need different settings
// Otherwise, JsonSerializerDefaults.Web is used automatically for JsonStream<T>
builder.Services.AddSingleton(new JsonSerializerOptions
{
    PropertyNameCaseInsensitive = true,
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase
});
3. Use in Minimal API Endpoints
// Automatically handles camelCase JSON from web clients
// No DI registration needed!
app.MapPost("/process", async (JsonStream<MyDto> items) =>
{
    await foreach (var item in items)
    {
        // Process each item as it arrives from the request
        Console.WriteLine($"Processing: {item.Name}");
    }
    return Results.Ok();
});

Example request body:

[
  {
    "id": 1,
    "name": "First Item",
    "value": 10.5
  },
  {
    "id": 2,
    "name": "Second Item",
    "value": 20.5
  }
]

Direct PipeReader usage without any binding

With Source Generation
var pipe = new Pipe();
// write JSON to the pipe object yourself ...

await foreach (var item in JsonStreamReader.ReadArrayAsync<MyDto>(
    pipe.Reader,
    typeInfo: MyJsonContext.Default.MyDto))
{
    // Process item
}
Without Source Generation
var pipe = new Pipe();
// write JSON to the pipe object yourself ...

// Option 1: No options (case-sensitive by default for JsonStreamReader)
// Note: JsonStream<T> binding uses Web defaults, but direct JsonStreamReader does not
await foreach (var item in JsonStreamReader.ReadArrayAsync<MyDto>(pipe.Reader))
{
    // Process item - requires exact property name casing
}

// Option 2: With custom options (recommended for camelCase JSON)
var options = new JsonSerializerOptions
{
    PropertyNameCaseInsensitive = true,
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};

await foreach (var item in JsonStreamReader.ReadArrayAsync<MyDto>(
    pipe.Reader,
    options: options))
{
    // Process item - works with camelCase
}

Advanced Usage

Custom JsonReaderOptions

var readerOptions = new JsonReaderOptions
{
    CommentHandling = JsonCommentHandling.Disallow,
    MaxDepth = 64
};

await foreach (var item in JsonStreamReader.ReadArrayAsync<MyDto>(
    pipe.Reader,
    typeInfo: typeInfo,
    readerOptions: readerOptions))
{
    // Process item
}

Tuning Buffer Size

For large datasets, you can adjust the initial buffer size:

await foreach (var item in JsonStreamReader.ReadArrayAsync<MyDto>(
    pipe.Reader,
    typeInfo: typeInfo,
    initialBufferSize: 128))  // Default is 32
{
    // Process item
}

Performance

The library uses ArrayPool-based buffer management for optimal memory efficiency:

Benchmark Results (1000 items):

  • Allocated: ~444 KB (96% from objects, 4% from overhead)
  • Gen0: 60.5 collections
  • Gen1: 50% fewer compared to List-based buffering
  • Gen2: Minimal

Key optimizations:

  • ArrayPool for zero-allocation buffer reuse
  • Pre-sized buffers to reduce resizing

Native AOT compatibility summary

JsonStreaming is compatible with Native AOT when using source-generated JSON:


<PropertyGroup>
    <PublishAot>true</PublishAot>
</PropertyGroup>

AOT-Compatible registration methods:

  • AddJsonStreamType - No reflection
  • AddJsonStreamTypes - No reflection
  • AddJsonStreamTypeInfo - No reflection
  • ⚠️ AddJsonStreamContext - Uses reflection (not AOT-compatible)

Without Source Generation:

  • ❌ Reflection-based deserialization is NOT compatible with Native AOT
  • Use source generation for AOT scenarios

How It Works

  1. Reads from PipeReader - Efficient streaming I/O using System.IO.Pipelines
  2. Parses JSON incrementally - Uses Utf8JsonReader for fast parsing
  3. Buffers per chunk - Collects items from each pipeline buffer read
  4. Yields items - Returns items asynchronously via IAsyncEnumerable<T>
  5. Reuses buffers - Returns ArrayPool buffers for zero-allocation streaming

Usage Recommendations

Below you can find the general use cases on when to use each approach. This is only a general guideline, and you should always test your own scenarios to determine the best approach for your application.

Scenario Recommended Approach
Native AOT applications Use source generation with explicit registration methods
High-performance services Use source generation for best performance
Rapid prototyping Use without source generation for simpler setup
Legacy codebases Use without source generation with JsonSerializerOptions
Mixed AOT/non-AOT Use source generation for consistency

Requirements

  • .NET 8.0, 9.0, or 10.0
  • ASP.NET Core (for JsonStream parameter binding)
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.
  • net8.0

    • No dependencies.
  • net9.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.0 347 11/19/2025

Initial release: Streaming JSON array deserialization with System.IO.Pipelines, Native AOT support, ArrayPool-based buffer management, ASP.NET Core Minimal API integration