未验证 提交 24900f83 编写于 作者: E Eirik Tsarpalis 提交者: GitHub

IAsyncEnumerable deserializer should tolerate custom queue converters (#51702)

* IAsyncEnumerable deserializer should tolerate custom queue converters

* Update src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Stream.cs
Co-authored-by: NLayomi Akinrinade <layomia@gmail.com>

* address feedback

* update ILLink suppressions
Co-authored-by: NLayomi Akinrinade <layomia@gmail.com>
上级 53489ef0
......@@ -35,7 +35,7 @@
<argument>ILLink</argument>
<argument>IL2070</argument>
<property name="Scope">member</property>
<property name="Target">M:System.Text.Json.Serialization.Metadata.JsonTypeInfo.#ctor(System.Type,System.Text.Json.JsonSerializerOptions)</property>
<property name="Target">M:System.Text.Json.Serialization.Metadata.JsonTypeInfo.#ctor(System.Type,System.Text.Json.Serialization.JsonConverter,System.Type,System.Text.Json.JsonSerializerOptions)</property>
</attribute>
<attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
<argument>ILLink</argument>
......
......@@ -9,6 +9,10 @@ internal sealed class QueueOfTConverter<TCollection, TElement>
: IEnumerableDefaultConverter<TCollection, TElement>
where TCollection : Queue<TElement>
{
/// <summary>Lazily initialized singleton for hardcoding by the IAsyncEnumerable streaming deserializer.</summary>
internal static QueueOfTConverter<TCollection, TElement> Instance = _instance ??= new();
private static QueueOfTConverter<TCollection, TElement>? _instance;
protected override void Add(in TElement value, ref ReadStack state)
{
((TCollection)state.Current.ReturnValue!).Enqueue(value);
......
......@@ -8,6 +8,7 @@
using System.IO;
using System.Runtime.CompilerServices;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Converters;
using System.Text.Json.Serialization.Metadata;
using System.Threading;
using System.Threading.Tasks;
......@@ -219,9 +220,11 @@ public static partial class JsonSerializer
[EnumeratorCancellation] CancellationToken cancellationToken)
{
var bufferState = new ReadAsyncBufferState(options.DefaultBufferSize);
// Hardcode the queue converter to avoid accidental use of custom converters
JsonConverter converter = QueueOfTConverter<Queue<TValue>, TValue>.Instance;
JsonTypeInfo jsonTypeInfo = new JsonTypeInfo(typeof(Queue<TValue>), converter, typeof(Queue<TValue>), options);
ReadStack readStack = default;
readStack.Initialize(typeof(Queue<TValue>), options, supportContinuation: true);
JsonConverter converter = readStack.Current.JsonPropertyInfo!.ConverterBase;
readStack.Initialize(jsonTypeInfo, supportContinuation: true);
var jsonReaderState = new JsonReaderState(options.GetReaderOptions());
try
......@@ -229,8 +232,8 @@ public static partial class JsonSerializer
do
{
bufferState = await ReadFromStreamAsync(utf8Json, bufferState, cancellationToken).ConfigureAwait(false);
ContinueDeserialize<Queue<TValue>>(ref bufferState, ref jsonReaderState, ref readStack, converter, options);
if (readStack.Current.ReturnValue is Queue<TValue> queue)
Queue<TValue>? queue = ContinueDeserialize<Queue<TValue>>(ref bufferState, ref jsonReaderState, ref readStack, converter, options);
if (queue is not null)
{
while (queue.Count > 0)
{
......
......@@ -160,18 +160,25 @@ internal JsonTypeInfo(Type type, JsonSerializerOptions options, ConverterStrateg
PropertyInfoForTypeInfo = null!;
}
internal JsonTypeInfo(Type type, JsonSerializerOptions options)
internal JsonTypeInfo(Type type, JsonSerializerOptions options) :
this(
type,
GetConverter(
type,
parentClassType: null, // A TypeInfo never has a "parent" class.
memberInfo: null, // A TypeInfo never has a "parent" property.
out Type runtimeType,
options),
runtimeType,
options)
{
}
internal JsonTypeInfo(Type type, JsonConverter converter, Type runtimeType, JsonSerializerOptions options)
{
Type = type;
Options = options;
JsonConverter converter = GetConverter(
Type,
parentClassType: null, // A TypeInfo never has a "parent" class.
memberInfo: null, // A TypeInfo never has a "parent" property.
out Type runtimeType,
Options);
JsonNumberHandling? typeNumberHandling = GetNumberHandlingForType(Type);
PropertyInfoForTypeInfo = CreatePropertyInfoForTypeInfo(Type, runtimeType, converter, typeNumberHandling, Options);
......
......@@ -304,7 +304,7 @@ private async IAsyncEnumerator<TElement> GetAsyncEnumeratorInner(CancellationTok
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
private class Utf8MemoryStream : MemoryStream
public class Utf8MemoryStream : MemoryStream
{
public Utf8MemoryStream() : base()
{
......
......@@ -7,6 +7,7 @@
using System.Threading;
using System.Threading.Tasks;
using Xunit;
using Utf8MemoryStream = System.Text.Json.Tests.Serialization.CollectionTests.Utf8MemoryStream;
namespace System.Text.Json.Serialization.Tests
{
......@@ -73,12 +74,74 @@ public static async Task DeserializeAsyncEnumerable_ReadSourceAsync<TElement>(IE
Assert.Equal(source, results);
}
[Fact]
public static async Task DeserializeAsyncEnumerable_ShouldTolerateCustomQueueConverters()
{
const int expectedCount = 20;
JsonSerializerOptions options = new JsonSerializerOptions
{
Converters = { new DegenerateQueueConverterFactory() }
};
byte[] data = JsonSerializer.SerializeToUtf8Bytes(Enumerable.Repeat(Enumerable.Repeat(1,3), expectedCount));
using var stream = new MemoryStream(data);
int callbackCount = 0;
await foreach (Queue<int> nestedQueue in JsonSerializer.DeserializeAsyncEnumerable<Queue<int>>(stream, options))
{
Assert.Equal(1, nestedQueue.Count);
Assert.Equal(0, nestedQueue.Peek());
callbackCount++;
}
Assert.Equal(expectedCount, callbackCount);
}
private class DegenerateQueueConverterFactory : JsonConverterFactory
{
public override bool CanConvert(Type typeToConvert) => typeToConvert.IsGenericType && typeof(Queue<>) == typeToConvert.GetGenericTypeDefinition();
public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options)
{
Type queueElement = typeToConvert.GetGenericArguments()[0];
Type converterType = typeof(DegenerateQueueConverter<>).MakeGenericType(queueElement);
return (JsonConverter)Activator.CreateInstance(converterType, nonPublic: true);
}
private class DegenerateQueueConverter<T> : JsonConverter<Queue<T>>
{
public override bool CanConvert(Type typeToConvert) => typeof(Queue<T>).IsAssignableFrom(typeToConvert);
public override Queue<T>? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
while (reader.Read() && reader.TokenType != JsonTokenType.EndArray);
var queue = new Queue<T>();
queue.Enqueue(default);
return queue;
}
public override void Write(Utf8JsonWriter writer, Queue<T> value, JsonSerializerOptions options) => throw new NotImplementedException();
}
}
[Fact]
public static void DeserializeAsyncEnumerable_NullStream_ThrowsArgumentNullException()
{
AssertExtensions.Throws<ArgumentNullException>("utf8Json", () => JsonSerializer.DeserializeAsyncEnumerable<int>(utf8Json: null));
}
[Theory]
[InlineData("42")]
[InlineData("\"\"")]
[InlineData("{}")]
public static async Task DeserializeAsyncEnumerable_NotARootLevelJsonArray_ThrowsJsonException(string json)
{
using var utf8Json = new Utf8MemoryStream(json);
IAsyncEnumerable<int> asyncEnumerable = JsonSerializer.DeserializeAsyncEnumerable<int>(utf8Json);
await using IAsyncEnumerator<int> enumerator = asyncEnumerable.GetAsyncEnumerator();
await Assert.ThrowsAsync<JsonException>(async () => await enumerator.MoveNextAsync());
}
[Fact]
public static async Task DeserializeAsyncEnumerable_CancellationToken_ThrowsOnCancellation()
{
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册