[API Proposal]: Allow Utf8JsonReader to be driven by a custom token source
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
評価
- 難易度
- 5/5
- 見積もり時間
- 1週間以上
- 初心者へのやさしさ
- 25/100
調査の方向性
Start by reading Utf8JsonReader and JsonSerializer implementation and tests in the .NET runtime, focusing on reader copies, look-ahead, and token value semantics. The proposal names no specific files or tests, so first locate the existing reader and serializer test suites and trace their current byte-based paths. Done means the proposed source-backed reader and synchronous deserialization APIs work with converters and generated metadata, preserve source positions, and avoid measurable regression to the existing read path.
索引モデルが issue の本文から書いたものです。
説明
Background and motivation
System.Text.Json has a mature serializer, metadata model, source generator and converter ecosystem. Many JSON-adjacent formats (JSON5, HJSON, JSONC variants, and anything that can produce a JSON-shaped token stream) would like to reuse all of that, but the only entry point is Utf8JsonReader, which can only tokenize strict JSON bytes.
JsonConverter<T>.Read takes a concrete ref struct Utf8JsonReader, so there is no way to supply tokens from another tokenizer. Today the only option is to transcode the input to JSON bytes and deserialize those. For example, Json5 tokenizes JSON5, writes it through Utf8JsonWriter into a buffer, and then calls JsonSerializer.Deserialize<T>. This works, but:
- The whole document (or every chunk) has to be re-encoded and copied.
JsonExceptionline/position information refers to the transcoded JSON, not to the user's source file. For config-file formats this is the main usability problem.- Every library has to rebuild the transcoding and buffering plumbing itself.
This proposal does not ask for System.Text.Json to support JSON5 or any other format. It asks for a format-neutral extension point: let a Utf8JsonReader pull tokens from a caller-supplied source that has already normalized each value to JSON.
API Proposal
namespace System.Text.Json;
public abstract class JsonTokenSource
{
/// Advances to the next token. Returns false at end of input.
/// `value` must be a normalized JSON representation of the token's value
/// (JSON string escaping, JSON number syntax), valid until the next call to Read.
public abstract bool Read(out JsonTokenType tokenType, out ReadOnlySpan<byte> value, out bool valueIsEscaped);
/// Position of the current token in the original input, used for TokenStartIndex and exception messages.
public abstract long TokenStartIndex { get; }
public virtual long LineNumber => -1;
public virtual long BytePositionInLine => -1;
/// Supports reader copies used for look-ahead (e.g. polymorphic metadata, read-ahead in the serializer).
public abstract object? Checkpoint();
public abstract void Restore(object? checkpoint);
}
public ref partial struct Utf8JsonReader
{
public Utf8JsonReader(JsonTokenSource source, JsonReaderOptions options = default);
}
public static partial class JsonSerializer
{
public static TValue? Deserialize<TValue>(JsonTokenSource source, JsonSerializerOptions? options = null);
public static TValue? Deserialize<TValue>(JsonTokenSource source, JsonTypeInfo<TValue> jsonTypeInfo);
public static object? Deserialize(JsonTokenSource source, Type returnType, JsonSerializerOptions? options = null);
}
When constructed with a source, Read() delegates to it, while depth tracking, structural validation, Skip(), TrySkip(), GetString(), TryGet*(), ValueTextEquals() and so on continue to work over the normalized values.
API Usage
public sealed class Json5TokenSource(ReadOnlyMemory<byte> json5) : JsonTokenSource
{
// Tokenizes JSON5; emits 'it\'s' as "it's", 0x1F as 31, unquoted keys as PropertyName, etc.
}
var config = JsonSerializer.Deserialize<AppConfig>(
new Json5TokenSource(File.ReadAllBytes("appsettings.json5")),
options); // all custom converters and source-generated metadata work unchanged
// Errors report the position in appsettings.json5:
// "'x' is an invalid start of a value. LineNumber: 12 | BytePositionInLine: 4."
Alternative Designs
- Interface instead of abstract class (
IJsonTokenSource). An abstract class leaves room to add members later (e.g.ValueSequencesupport) without breaking implementers. - Exception position remapping only: a hook on
JsonSerializerOptionsthat rewritesLineNumber/BytePositionInLinein aJsonException. This is much smaller and fixes the biggest usability problem for transcoding libraries, but still requires the copy. - Status quo: transcode to JSON and deserialize. This works, but has the costs described above.
Risks
Utf8JsonReader.Read()is a very hot path. The extra branch must not measurably regress the existing byte-based path. Keeping the source-backed path out of line behind a single null/flag check should keep it close to free.- Reader copies: the serializer copies the reader struct for look-ahead and restore. A stateful source has to support that, hence
Checkpoint/Restore. Code that assumes a copied reader is fully independent needs auditing. ValueSpan,HasValueSequenceandValueIsEscapedsemantics: the source must guarantee that values are valid JSON tokens. Invalid normalized values should fail with the same errors as invalid JSON.- Async/streaming deserialization (
DeserializeAsync, resumableJsonReaderState) is out of scope for the first version. Sync-only overloads are proposed. JsonDocument/JsonNodeparsing from a source is out of scope here and could be added later.
- 主要言語
- C#
- スター
- 18.4k
- フォーク
- 5.8k
- 平均マージ
- 2日 13時間
- マージ済み PR(30日)
- 588
環境構築
このプロジェクトの開発コンテナを、あなたの GitHub アカウントでブラウザ上に起動します。
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートあり
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
dotnet/runtime のほかの issue
-
area-System.IO untriaged
難易度 2/5 半日 初心者へのやさしさ 72/100
dotnet/runtime#136802 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
[wasi] .NET 10: WasmEnableSIMD with AOT fails because WasiApp.targets has -msimd128 commented outオープンarea-Build-mono untriaged
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
メンテナーはふだん 1 日以内に返信
-
area-System.ComponentModel untriaged
難易度 2/5 1〜3時間 初心者へのやさしさ 65/100
dotnet/runtime#136608 · コメント 2 件 ·
メンテナーはふだん 1 日以内に返信
-
MemoryMappedFile (Windows): failed MEM_COMMIT is ignored, accessor over reserved memory crashes the process対応中かも @RutulPatel8 が 2 日前に担当しました。 オープンarea-System.IO bug untriaged
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
dotnet/runtime#136599 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
area-System.IO untriaged
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
dotnet/runtime#136577 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
似ている issue
-
VB -> C#
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
icsharpcode/CodeConverter#1301 ·
メンテナーはふだん 3 日以内に返信
-
PettingZoo wrappers send only the first action component to every action dimension対応中かも @RaphaelFakhri が今日担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 73/100
Unity-Technologies/ml-agents#6341 ·
メンテナーはふだん 1 日以内に返信
-
Area: World-Readiness
難易度 1/5 1時間未満 初心者へのやさしさ 90/100
microsoft/calculator#2522 ·
-
needs-triage
難易度 2/5 1〜3時間 初心者へのやさしさ 74/100
Sonarr/Sonarr#9274 · コメント 1 件 ·
メンテナーはふだん 2 日以内に返信
-
enhancement
難易度 2/5 1〜3時間 初心者へのやさしさ 64/100