[英]Deserializing to AsyncEnumerable using Newtonsoft.Json
System.Text.Json.JsonSerializer.DeserializeAsyncEnumerable<T>
是 System.Text.Json 的一種方法,它可以采用Stream
並生成IAsyncEnumerable<T>
,其中枚舉可以是異步的。 例如,這對於反序列化由網絡連接流式傳輸的元素數組很有用,因此我們可以在到達流的實際末尾之前輸出元素。
有什么方法可以使用 Newtonsoft.Json 庫實現等效功能?
Json.NET 的JsonSerializer
類不支持異步反序列化,未來計划不支持。 如需確認,請參閱例如以下未解決的問題:
功能請求:與 System.IO.Pipelines API #1795 進行異步序列化:
使整個序列化器異步將是一項巨大的工作,並且需要大量重復的代碼才能擁有同步和異步路徑。 我真的不想保持這種狀態。
然而, JsonTextReader
確實支持異步讀取,並且 LINQ-to-JSON 支持通過JToken.LoadAsync()
進行異步加載。 因此,您應該能夠創建一個IAsyncEnumerable
來遍歷其根值為數組的 JSON 流,並將每個數組元素作為JToken
異步返回。 隨后,您可以使用JToken.ToObject<T>()
將每個令牌反序列化為最終模型。
首先,創建以下擴展方法:
public static partial class JsonExtensions
{
/// <summary>
/// Asynchronously load and synchronously deserialize values from a stream containing a JSON array. The root object of the JSON stream must in fact be an array, or an exception is thrown
/// </summary>
public static async IAsyncEnumerable<T?> DeserializeAsyncEnumerable<T>(Stream stream, JsonSerializerSettings? settings = default, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var serializer = JsonSerializer.CreateDefault(settings);
var loadSettings = new JsonLoadSettings { LineInfoHandling = LineInfoHandling.Ignore }; // For performance do not load line info.
// StreamReader and JsonTextReader do not implement IAsyncDisposable so let the caller dispose the stream.
using (var textReader = new StreamReader(stream, leaveOpen : true))
using (var reader = new JsonTextReader(textReader) { CloseInput = false })
{
await foreach (var token in LoadAsyncEnumerable(reader, loadSettings, cancellationToken ).ConfigureAwait(false))
yield return token.ToObject<T>(serializer);
}
}
/// <summary>
/// Asynchronously load and return JToken values from a stream containing a JSON array. The root object of the JSON stream must in fact be an array, or an exception is thrown
/// </summary>
public static async IAsyncEnumerable<JToken> LoadJTokenAsyncEnumerable(Stream stream, JsonLoadSettings? settings = default, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
// StreamReader and JsonTextReader do not implement IAsyncDisposable so let the caller dispose the stream.
using (var textReader = new StreamReader(stream, leaveOpen : true))
using (var reader = new JsonTextReader(textReader) { CloseInput = false })
{
await foreach (var token in LoadAsyncEnumerable(reader, settings, cancellationToken).ConfigureAwait(false))
yield return token;
}
}
/// <summary>
/// Asynchronously load and return JToken values from a stream containing a JSON array. The root object of the JSON stream must in fact be an array, or an exception is thrown
/// </summary>
public static async IAsyncEnumerable<JToken> LoadAsyncEnumerable(JsonTextReader reader, JsonLoadSettings? settings = default, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
(await reader.MoveToContentAndAssertAsync().ConfigureAwait(false)).AssertTokenType(JsonToken.StartArray);
cancellationToken.ThrowIfCancellationRequested();
while ((await reader.ReadToContentAndAssert(cancellationToken).ConfigureAwait(false)).TokenType != JsonToken.EndArray)
{
cancellationToken.ThrowIfCancellationRequested();
yield return await JToken.LoadAsync(reader, settings, cancellationToken).ConfigureAwait(false);
}
cancellationToken.ThrowIfCancellationRequested();
}
public static JsonReader AssertTokenType(this JsonReader reader, JsonToken tokenType) =>
reader.TokenType == tokenType ? reader : throw new JsonSerializationException(string.Format("Unexpected token {0}, expected {1}", reader.TokenType, tokenType));
public static async Task<JsonReader> ReadToContentAndAssert(this JsonReader reader, CancellationToken cancellationToken = default) =>
await (await reader.ReadAndAssertAsync(cancellationToken).ConfigureAwait(false)).MoveToContentAndAssertAsync(cancellationToken).ConfigureAwait(false);
public static async Task<JsonReader> MoveToContentAndAssertAsync(this JsonReader reader, CancellationToken cancellationToken = default)
{
if (reader == null)
throw new ArgumentNullException();
if (reader.TokenType == JsonToken.None) // Skip past beginning of stream.
await reader.ReadAndAssertAsync(cancellationToken).ConfigureAwait(false);
while (reader.TokenType == JsonToken.Comment) // Skip past comments.
await reader.ReadAndAssertAsync(cancellationToken).ConfigureAwait(false);
return reader;
}
public static async Task<JsonReader> ReadAndAssertAsync(this JsonReader reader, CancellationToken cancellationToken = default)
{
if (reader == null)
throw new ArgumentNullException();
if (!await reader.ReadAsync(cancellationToken).ConfigureAwait(false))
throw new JsonReaderException("Unexpected end of JSON stream.");
return reader;
}
}
現在您將能夠執行以下操作:
await foreach (var token in JsonExtensions.LoadJTokenAsyncEnumerable(stream, cancellationToken : cancellationToken))
{
Console.WriteLine(token.ToString(Formatting.None));
}
筆記:
取消未測試。 如果取消,上述實現應該拋出OperationCanceledException
。
System.Text.Json
是從頭開始設計的,以支持具有良好性能的異步反序列化。 如果你需要異步反序列化,你應該考慮重寫你的代碼來使用它。
StreamReader
和JsonTextReader
不實現IAsyncDisposable
,因此擴展方法使底層流保持打開狀態,以供調用者處置。
我不確定 .NET 6 中是否仍需要所有這些.ConfigureAwait(false)
調用。
在這里經過輕微測試的演示小提琴。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.