Description
Background and motivation
When we originally did System.Text.Json there was some discussion about adding overloads to support PipeReader and PipeWriter. These discussion died because we didn't want to decide on forcing every API that had a Stream overload to have one for PipeReader/PipeWriter (some of that is here #28325). ASP.NET Core can save on allocations and excess buffer copies if we had support for these primitives in the JSON serializer. This likely isn't the only change we need to make to get great performance (there are some tweaks to pipelines that can be made to improve buffer sizes), but the idea is to add the APIs so that we can improve this on top.
Today in ASP.NET Core, we use the Stream overloads and:
- On the Deserialize side, there's no need to further copy that into a buffer before parsing JSON.
- On the Serialize side, we can avoid, the PooledBufferWriter allocation, and the extra buffer copies into the underlying Stream (which wrap the PipeWriter).
Of course we should measure and see how this materializes but we need to expose APIs to do so (at least privately so that ASP.NET Core can properly plumb them through the chain).
API Proposal
namespace System.Collections.Generic;
public static class JsonSerializer
{
public static Task SerializeAsync<TValue>(
PipeWriter utf8Json,
TValue value,
JsonTypeInfo<TValue> jsonTypeInfo,
CancellationToken cancellationToken = default);
public static Task SerializeAsync<TValue>(
PipeWriter utf8Json,
TValue value,
JsonSerializerOptions? options = null,
CancellationToken cancellationToken = default);
public static Task SerializeAsync(
PipeWriter utf8Json,
object? value,
JsonTypeInfo jsonTypeInfo,
CancellationToken cancellationToken = default);
public static Task SerializeAsync(
PipeWriter utf8Json,
object? value,
Type inputType,
JsonSerializerContext context,
CancellationToken cancellationToken = default)
public static ValueTask<TValue?> DeserializeAsync<TValue>(
PipeReader utf8Json,
JsonSerializerOptions? options = null,
CancellationToken cancellationToken = default);
public static ValueTask<TValue?> DeserializeAsync<TValue>(
PipeReader utf8Json,
JsonTypeInfo<TValue> jsonTypeInfo,
CancellationToken cancellationToken = default);
public static ValueTask<object?> DeserializeAsync(
PipeReader utf8Json,
JsonTypeInfo jsonTypeInfo,
CancellationToken cancellationToken = default);
public static ValueTask<object?> DeserializeAsync(
PipeReader utf8Json,
Type returnType,
JsonSerializerContext context,
CancellationToken cancellationToken = default);
}
API Usage
var pipe = new Pipe();
await JsonSerialize.SerilizeAsync(pipe.Writer, new Person { Name = "David" });
await pipe.Writer.CompleteAsync();
var result = await JsonSerialize.DeserilizeAsync<Person>(pipe.Reader);
await pipe.Reader.CompleteAsync();
Alternative Designs
No response
Risks
No response