diff --git a/src/NuGet.Core/NuGet.ProjectModel/Utf8JsonReaderExtensions.cs b/src/NuGet.Core/NuGet.ProjectModel/Utf8JsonReaderExtensions.cs
new file mode 100644
index 00000000000..7b40b3f1b30
--- /dev/null
+++ b/src/NuGet.Core/NuGet.ProjectModel/Utf8JsonReaderExtensions.cs
@@ -0,0 +1,40 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Text.Json;
+
+namespace NuGet.ProjectModel
+{
+ internal static class Utf8JsonReaderExtensions
+ {
+ internal static string ReadTokenAsString(this ref Utf8JsonReader reader)
+ {
+ switch (reader.TokenType)
+ {
+ case JsonTokenType.True:
+ return bool.TrueString;
+ case JsonTokenType.False:
+ return bool.FalseString;
+ case JsonTokenType.Number:
+ return reader.ReadNumberAsString();
+ case JsonTokenType.String:
+ return reader.GetString();
+ case JsonTokenType.None:
+ case JsonTokenType.Null:
+ return null;
+ default:
+ throw new InvalidCastException();
+ }
+ }
+
+ private static string ReadNumberAsString(this ref Utf8JsonReader reader)
+ {
+ if (reader.TryGetInt64(out long value))
+ {
+ return value.ToString();
+ }
+ return reader.GetDouble().ToString();
+ }
+ }
+}
diff --git a/src/NuGet.Core/NuGet.ProjectModel/Utf8JsonStreamReader.cs b/src/NuGet.Core/NuGet.ProjectModel/Utf8JsonStreamReader.cs
new file mode 100644
index 00000000000..0dc89846b86
--- /dev/null
+++ b/src/NuGet.Core/NuGet.ProjectModel/Utf8JsonStreamReader.cs
@@ -0,0 +1,273 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Buffers;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text.Json;
+
+namespace NuGet.ProjectModel
+{
+ ///
+ /// This struct is used to read over a memeory stream in parts, in order to avoid reading the entire stream into memory.
+ /// It functions as a wrapper around , while maintaining a stream and a buffer to read from.
+ ///
+ internal ref struct Utf8JsonStreamReader
+ {
+ private static readonly char[] DelimitedStringDelimiters = [' ', ','];
+ private static readonly byte[] Utf8Bom = [0xEF, 0xBB, 0xBF];
+
+ private const int BufferSizeDefault = 16 * 1024;
+ private const int MinBufferSize = 1024;
+ private Utf8JsonReader _reader;
+#pragma warning disable CA2213 // Disposable fields should be disposed
+ private Stream _stream;
+#pragma warning restore CA2213 // Disposable fields should be disposed
+ // The buffer is used to read from the stream in chunks.
+ private byte[] _buffer;
+ private bool _disposed;
+ private ArrayPool _bufferPool;
+ private int _bufferUsed = 0;
+
+ internal Utf8JsonStreamReader(Stream stream, int bufferSize = BufferSizeDefault, ArrayPool arrayPool = null)
+ {
+ if (stream is null)
+ {
+ throw new ArgumentNullException(nameof(stream));
+ }
+
+ if (bufferSize < MinBufferSize)
+ {
+ throw new ArgumentException($"Buffer size must be at least {MinBufferSize} bytes", nameof(bufferSize));
+ }
+
+ _bufferPool = arrayPool ?? ArrayPool.Shared;
+ _buffer = _bufferPool.Rent(bufferSize);
+ _disposed = false;
+ _stream = stream;
+ _stream.Read(_buffer, 0, 3);
+ if (!Utf8Bom.AsSpan().SequenceEqual(_buffer.AsSpan(0, 3)))
+ {
+ _bufferUsed = 3;
+ }
+
+ var iniialJsonReaderState = new JsonReaderState(new JsonReaderOptions
+ {
+ AllowTrailingCommas = true,
+ CommentHandling = JsonCommentHandling.Skip,
+ });
+
+ ReadStreamIntoBuffer(iniialJsonReaderState);
+ _reader.Read();
+ }
+
+ internal bool IsFinalBlock => _reader.IsFinalBlock;
+
+ internal JsonTokenType TokenType => _reader.TokenType;
+
+ internal bool ValueTextEquals(ReadOnlySpan utf8Text) => _reader.ValueTextEquals(utf8Text);
+
+ internal bool TryGetInt32(out int value) => _reader.TryGetInt32(out value);
+
+ internal string GetString() => _reader.GetString();
+
+ internal bool GetBoolean() => _reader.GetBoolean();
+
+ internal int GetInt32() => _reader.GetInt32();
+
+ internal bool Read()
+ {
+ ThrowExceptionIfDisposed();
+
+ bool wasRead;
+ while (!(wasRead = _reader.Read()) && !_reader.IsFinalBlock)
+ {
+ GetMoreBytesFromStream();
+ }
+ return wasRead;
+ }
+
+ internal void Skip()
+ {
+ ThrowExceptionIfDisposed();
+
+ bool wasSkipped;
+ while (!(wasSkipped = _reader.TrySkip()) && !_reader.IsFinalBlock)
+ {
+ GetMoreBytesFromStream();
+ }
+ if (!wasSkipped)
+ {
+ _reader.Skip();
+ }
+ }
+
+ internal string ReadNextTokenAsString()
+ {
+ ThrowExceptionIfDisposed();
+
+ if (Read())
+ {
+ return _reader.ReadTokenAsString();
+ }
+
+ return null;
+ }
+
+ internal IList ReadStringArrayAsIList(IList strings = null)
+ {
+ if (TokenType == JsonTokenType.StartArray)
+ {
+ while (Read() && TokenType != JsonTokenType.EndArray)
+ {
+ string value = _reader.ReadTokenAsString();
+
+ strings = strings ?? new List();
+
+ strings.Add(value);
+ }
+ }
+ return strings;
+ }
+
+ internal IReadOnlyList ReadDelimitedString()
+ {
+ ThrowExceptionIfDisposed();
+
+ if (Read())
+ {
+ switch (TokenType)
+ {
+ case JsonTokenType.String:
+ var value = GetString();
+
+ return value.Split(DelimitedStringDelimiters, StringSplitOptions.RemoveEmptyEntries);
+
+ default:
+ var invalidCastException = new InvalidCastException();
+ throw new JsonException(invalidCastException.Message, invalidCastException);
+ }
+ }
+
+ return null;
+ }
+
+ internal bool ReadNextTokenAsBoolOrFalse()
+ {
+ ThrowExceptionIfDisposed();
+
+ if (Read() && (TokenType == JsonTokenType.False || TokenType == JsonTokenType.True))
+ {
+ return GetBoolean();
+ }
+ return false;
+ }
+
+ internal IReadOnlyList ReadNextStringOrArrayOfStringsAsReadOnlyList()
+ {
+ ThrowExceptionIfDisposed();
+
+ if (Read())
+ {
+ switch (_reader.TokenType)
+ {
+ case JsonTokenType.String:
+ return new[] { (string)_reader.GetString() };
+
+ case JsonTokenType.StartArray:
+ return ReadStringArrayAsReadOnlyListFromArrayStart();
+
+ case JsonTokenType.StartObject:
+ return null;
+ }
+ }
+
+ return null;
+ }
+
+ internal IReadOnlyList ReadStringArrayAsReadOnlyListFromArrayStart()
+ {
+ ThrowExceptionIfDisposed();
+
+ List strings = null;
+
+ while (Read() && _reader.TokenType != JsonTokenType.EndArray)
+ {
+ string value = _reader.ReadTokenAsString();
+
+ strings = strings ?? new List();
+
+ strings.Add(value);
+ }
+
+ return (IReadOnlyList)strings ?? Array.Empty();
+ }
+
+ // This function is called when Read() returns false and we're not already in the final block
+ private void GetMoreBytesFromStream()
+ {
+ if (_reader.BytesConsumed < _bufferUsed)
+ {
+ // If the number of bytes consumed by the reader is less than the amount set in the buffer then we have leftover bytes
+ var oldBuffer = _buffer;
+ ReadOnlySpan leftover = oldBuffer.AsSpan((int)_reader.BytesConsumed);
+ _bufferUsed = leftover.Length;
+
+ // If the leftover bytes are the same as the buffer size then we are at capacity and need to double the buffer size
+ if (leftover.Length == _buffer.Length)
+ {
+ _buffer = _bufferPool.Rent(_buffer.Length * 2);
+ leftover.CopyTo(_buffer);
+ _bufferPool.Return(oldBuffer, true);
+ }
+ else
+ {
+ leftover.CopyTo(_buffer);
+ }
+ }
+ else
+ {
+ _bufferUsed = 0;
+ }
+
+ ReadStreamIntoBuffer(_reader.CurrentState);
+ }
+
+ ///
+ /// Loops through the stream and reads it into the buffer until the buffer is full or the stream is empty, creates the Utf8JsonReader.
+ ///
+ private void ReadStreamIntoBuffer(JsonReaderState jsonReaderState)
+ {
+ int bytesRead;
+ do
+ {
+ var spaceLeftInBuffer = _buffer.Length - _bufferUsed;
+ bytesRead = _stream.Read(_buffer, _bufferUsed, spaceLeftInBuffer);
+ _bufferUsed += bytesRead;
+ }
+ while (bytesRead != 0 && _bufferUsed != _buffer.Length);
+ _reader = new Utf8JsonReader(_buffer.AsSpan(0, _bufferUsed), isFinalBlock: bytesRead == 0, jsonReaderState);
+ }
+
+ public void Dispose()
+ {
+ if (!_disposed)
+ {
+ _disposed = true;
+ byte[] toReturn = _buffer;
+ _buffer = null!;
+ _bufferPool.Return(toReturn, true);
+ }
+ }
+
+ private void ThrowExceptionIfDisposed()
+ {
+ if (_disposed)
+ {
+ throw new ObjectDisposedException(nameof(Utf8JsonStreamReader));
+ }
+ }
+ }
+}
diff --git a/src/NuGet.Core/NuGet.ProjectModel/Utf8JsonStreamReaderConverter.cs b/src/NuGet.Core/NuGet.ProjectModel/Utf8JsonStreamReaderConverter.cs
new file mode 100644
index 00000000000..167750c3828
--- /dev/null
+++ b/src/NuGet.Core/NuGet.ProjectModel/Utf8JsonStreamReaderConverter.cs
@@ -0,0 +1,13 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+namespace NuGet.ProjectModel
+{
+ ///
+ /// An abstract class that defines a function for reading a into a
+ ///
+ ///
+ internal abstract class Utf8JsonStreamReaderConverter
+ {
+ public abstract T Read(ref Utf8JsonStreamReader reader);
+ }
+}
diff --git a/test/NuGet.Core.Tests/NuGet.ProjectModel.Test/LockFileFormatTests.cs b/test/NuGet.Core.Tests/NuGet.ProjectModel.Test/LockFileFormatTests.cs
index 54977dc03b8..310781aa194 100644
--- a/test/NuGet.Core.Tests/NuGet.ProjectModel.Test/LockFileFormatTests.cs
+++ b/test/NuGet.Core.Tests/NuGet.ProjectModel.Test/LockFileFormatTests.cs
@@ -213,7 +213,6 @@ public void LockFileFormat_ReadsLockFileWithNoTools()
var target = lockFile.Targets.Single();
Assert.Equal(NuGetFramework.Parse("dotnet"), target.TargetFramework);
-
var runtimeTargetLibrary = target.Libraries.Single();
Assert.Equal("System.Runtime", runtimeTargetLibrary.Name);
Assert.Equal(NuGetVersion.Parse("4.0.20-beta-22927"), runtimeTargetLibrary.Version);
diff --git a/test/NuGet.Core.Tests/NuGet.ProjectModel.Test/Utf8JsonReaderExtensionsTests.cs b/test/NuGet.Core.Tests/NuGet.ProjectModel.Test/Utf8JsonReaderExtensionsTests.cs
new file mode 100644
index 00000000000..0c60a72b4e3
--- /dev/null
+++ b/test/NuGet.Core.Tests/NuGet.ProjectModel.Test/Utf8JsonReaderExtensionsTests.cs
@@ -0,0 +1,35 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Text;
+using System.Text.Json;
+using Xunit;
+
+namespace NuGet.ProjectModel.Test
+{
+ [UseCulture("")] // Fix tests failing on systems with non-English locales
+ public class Utf8JsonReaderExtensionsTests
+ {
+ [Theory]
+ [InlineData("null", null)]
+ [InlineData("true", "True")]
+ [InlineData("false", "False")]
+ [InlineData("-2", "-2")]
+ [InlineData("9223372036854775807", "9223372036854775807")]
+ [InlineData("3.14", "3.14")]
+ [InlineData("\"b\"", "b")]
+ public void ReadTokenAsString_WhenValueIsConvertibleToString_ReturnsValueAsString(
+ string value,
+ string expectedResult)
+ {
+ var json = $"{{\"a\":{value}}}";
+ var encodedBytes = Encoding.UTF8.GetBytes(json);
+ var reader = new Utf8JsonReader(encodedBytes);
+ reader.Read();
+ reader.Read();
+ reader.Read();
+ string actualResult = reader.ReadTokenAsString();
+ Assert.Equal(expectedResult, actualResult);
+ }
+ }
+}
diff --git a/test/NuGet.Core.Tests/NuGet.ProjectModel.Test/Utf8JsonStreamReaderTests.cs b/test/NuGet.Core.Tests/NuGet.ProjectModel.Test/Utf8JsonStreamReaderTests.cs
new file mode 100644
index 00000000000..3193f43c4f5
--- /dev/null
+++ b/test/NuGet.Core.Tests/NuGet.ProjectModel.Test/Utf8JsonStreamReaderTests.cs
@@ -0,0 +1,833 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Buffers;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Text.Json;
+using Moq;
+using Xunit;
+
+namespace NuGet.ProjectModel.Test
+{
+ [UseCulture("")] // Fix tests failing on systems with non-English locales
+ public class Utf8JsonStreamReaderTests
+ {
+ private static readonly string JsonWithOverflowObject = "{\"object1\":{\"a\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"b\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"c\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"d\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"e\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"f\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"g\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"h\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"i\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"j\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"k\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"l\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"m\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"n\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"o\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"p\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"q\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"r\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"s\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"t\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"u\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\"},\"object2\":{\"a\":\"abcdefghijklmnopqrstuvwxyz\",\"b\":\"abcdefghijklmnopqrstuvwxyz\",\"c\":\"abcdefghijklmnopqrstuvwxyz\",\"d\":\"abcdefghijklmnopqrstuvwxyz\",\"e\":\"abcdefghijklmnopqrstuvwxyz\",\"f\":\"abcdefghijklmnopqrstuvwxyz\",\"g\":\"abcdefghijklmnopqrstuvwxyz\",\"h\":\"abcdefghijklmnopqrstuvwxyz\",\"i\":\"abcdefghijklmnopqrstuvwxyz\",\"j\":\"abcdefghijklmnopqrstuvwxyz\",\"k\":\"abcdefghijklmnopqrstuvwxyz\",\"l\":\"abcdefghijklmnopqrstuvwxyz\",\"m\":\"abcdefghijklmnopqrstuvwxyz\",\"n\":\"abcdefghijklmnopqrstuvwxyz\",\"o\":\"abcdefghijklmnopqrstuvwxyz\",\"p\":\"abcdefghijklmnopqrstuvwxyz\",\"q\":\"abcdefghijklmnopqrstuvwxyz\",\"r\":\"abcdefghijklmnopqrstuvwxyz\",\"s\":\"abcdefghijklmnopqrstuvwxyz\",\"t\":\"abcdefghijklmnopqrstuvwxyz\",\"u\":\"abcdefghijklmnopqrstuvwxyz\"}}";
+ private static readonly string JsonWithoutOverflow = "{\"object1\":{\"a\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"b\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"c\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"d\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"e\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"f\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"g\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"h\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"i\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"j\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"k\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"l\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"m\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"n\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"o\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"p\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"q\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"r\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"s\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"t\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"u\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\"}}";
+ private static readonly string JsonWithOverflow = "{\"object1\":{\"a\":\"abcdefghijklmnopqrstuvwxyz\",\"b\":\"abcdefghijklmnopqrstuvwxyz\",\"c\":\"abcdefghijklmnopqrstuvwxyz\",\"d\":\"abcdefghijklmnopqrstuvwxyz\",\"e\":\"abcdefghijklmnopqrstuvwxyz\",\"f\":\"abcdefghijklmnopqrstuvwxyz\",\"g\":\"abcdefghijklmnopqrstuvwxyz\",\"h\":\"abcdefghijklmnopqrstuvwxyz\",\"i\":\"abcdefghijklmnopqrstuvwxyz\",\"j\":\"abcdefghijklmnopqrstuvwxyz\",\"k\":\"abcdefghijklmnopqrstuvwxyz\",\"l\":\"abcdefghijklmnopqrstuvwxyz\",\"m\":\"abcdefghijklmnopqrstuvwxyz\",\"n\":\"abcdefghijklmnopqrstuvwxyz\",\"o\":\"abcdefghijklmnopqrstuvwxyz\",\"p\":\"abcdefghijklmnopqrstuvwxyz\",\"q\":\"abcdefghijklmnopqrstuvwxyz\",\"r\":\"abcdefghijklmnopqrstuvwxyz\",\"s\":\"abcdefghijklmnopqrstuvwxyz\",\"t\":\"abcdefghijklmnopqrstuvwxyz\",\"u\":\"abcdefghijklmnopqrstuvwxyz\"}, \"object2\": {\"a\":\"abcdefghijklmnopqrstuvwxyz\",\"b\":\"abcdefghijklmnopqrstuvwxyz\",\"c\":\"abcdefghijklmnopqrstuvwxyz\",\"d\":\"abcdefghijklmnopqrstuvwxyz\",\"e\":\"abcdefghijklmnopqrstuvwxyz\",\"f\":\"abcdefghijklmnopqrstuvwxyz\",\"g\":\"abcdefghijklmnopqrstuvwxyz\",\"h\":\"abcdefghijklmnopqrstuvwxyz\",\"i\":\"abcdefghijklmnopqrstuvwxyz\",\"j\":\"abcdefghijklmnopqrstuvwxyz\",\"k\":\"abcdefghijklmnopqrstuvwxyz\",\"l\":\"abcdefghijklmnopqrstuvwxyz\",\"m\":\"abcdefghijklmnopqrstuvwxyz\",\"n\":\"abcdefghijklmnopqrstuvwxyz\",\"o\":\"abcdefghijklmnopqrstuvwxyz\",\"p\":\"abcdefghijklmnopqrstuvwxyz\",\"q\":\"abcdefghijklmnopqrstuvwxyz\",\"r\":\"abcdefghijklmnopqrstuvwxyz\",\"s\":\"abcdefghijklmnopqrstuvwxyz\",\"t\":\"abcdefghijklmnopqrstuvwxyz\",\"u\":\"abcdefghijklmnopqrstuvwxyz\"}, \"object3\":{\"a\":\"abcdefghijklmnopqrstuvwxyz\",\"b\":\"abcdefghijklmnopqrstuvwxyz\",\"c\":\"abcdefghijklmnopqrstuvwxyz\",\"d\":\"abcdefghijklmnopqrstuvwxyz\",\"e\":\"abcdefghijklmnopqrstuvwxyz\",\"f\":\"abcdefghijklmnopqrstuvwxyz\",\"g\":\"abcdefghijklmnopqrstuvwxyz\",\"h\":\"abcdefghijklmnopqrstuvwxyz\",\"i\":\"abcdefghijklmnopqrstuvwxyz\",\"j\":\"abcdefghijklmnopqrstuvwxyz\",\"k\":\"abcdefghijklmnopqrstuvwxyz\",\"l\":\"abcdefghijklmnopqrstuvwxyz\",\"m\":\"abcdefghijklmnopqrstuvwxyz\",\"n\":\"abcdefghijklmnopqrstuvwxyz\",\"o\":\"abcdefghijklmnopqrstuvwxyz\",\"p\":\"abcdefghijklmnopqrstuvwxyz\",\"q\":\"abcdefghijklmnopqrstuvwxyz\",\"r\":\"abcdefghijklmnopqrstuvwxyz\",\"s\":\"abcdefghijklmnopqrstuvwxyz\",\"t\":\"abcdefghijklmnopqrstuvwxyz\",\"u\":\"abcdefghijklmnopqrstuvwxyz\"}}";
+ private static readonly string SmallJson = "{\"object1\":{\"a\":\"abcdefghijklmnopqrstuvwxyz\"}}";
+
+ [Fact]
+ public void Utf8JsonStreamReaderCtr_WhenStreamIsNull_Throws()
+ {
+ Assert.Throws(() =>
+ {
+ using var reader = new Utf8JsonStreamReader(null);
+ });
+ }
+
+ [Fact]
+ public void Utf8JsonStreamReaderCtr_WhenBufferToSmall_Throws()
+ {
+ Assert.Throws(() =>
+ {
+ var json = "{}";
+
+ using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(json)))
+ using (var reader = new Utf8JsonStreamReader(stream, 10))
+ {
+ }
+ });
+ }
+
+ [Fact]
+ public void Utf8JsonStreamReaderCtr_WhenStreamStartsWithUtf8Bom_SkipThem()
+ {
+ var json = Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble()) + "{}";
+
+ using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(json)))
+ using (var reader = new Utf8JsonStreamReader(stream))
+ {
+ Assert.Equal(5, stream.Position);
+ Assert.Equal(JsonTokenType.StartObject, reader.TokenType);
+ }
+ }
+
+ [Fact]
+ public void Utf8JsonStreamReaderCtr_WhenStreamStartsWithoutUtf8Bom_ReadFromStart()
+ {
+ var json = "{}";
+
+ using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(json)))
+ using (var reader = new Utf8JsonStreamReader(stream))
+ {
+ Assert.Equal(2, stream.Position);
+ Assert.Equal(JsonTokenType.StartObject, reader.TokenType);
+ }
+ }
+
+ [Fact]
+ public void Utf8JsonStreamReaderCtr_WhenReadingWithOverflow_FinalBlockFalse()
+ {
+ var json = Encoding.UTF8.GetBytes(JsonWithOverflowObject);
+ using (var stream = new MemoryStream(json))
+ using (var reader = new Utf8JsonStreamReader(stream, 1024))
+ {
+ Assert.False(reader.IsFinalBlock);
+ }
+ }
+
+ [Fact]
+ public void Read_WhenReadingMalfornedJsonString_Throws()
+ {
+ var json = Encoding.UTF8.GetBytes("{\"a\":\"string}");
+
+ Assert.ThrowsAny(() =>
+ {
+ using (var stream = new MemoryStream(json))
+ using (var reader = new Utf8JsonStreamReader(stream))
+ {
+ Assert.True(reader.IsFinalBlock);
+ Assert.Equal(JsonTokenType.StartObject, reader.TokenType);
+ reader.Read();
+ Assert.Equal(JsonTokenType.PropertyName, reader.TokenType);
+ reader.Read();
+ }
+ });
+ }
+
+ [Fact]
+ public void Read_WhenReadingMalfornedJson_Throws()
+ {
+ var json = Encoding.UTF8.GetBytes("{\"a\":\"string\"}ohno");
+ Assert.ThrowsAny(() =>
+ {
+ using (var stream = new MemoryStream(json))
+ using (var reader = new Utf8JsonStreamReader(stream))
+ {
+ Assert.True(reader.IsFinalBlock);
+ Assert.Equal(JsonTokenType.StartObject, reader.TokenType);
+ reader.Read();
+ Assert.Equal(JsonTokenType.PropertyName, reader.TokenType);
+ reader.Read();
+ reader.Read();
+ reader.Read();
+ reader.Read();
+ }
+ });
+ }
+
+ [Fact]
+ public void Read_WhenReadingSmallJson_Read()
+ {
+ var json = Encoding.UTF8.GetBytes(SmallJson);
+
+ using (var stream = new MemoryStream(json))
+ using (var reader = new Utf8JsonStreamReader(stream))
+ {
+ Assert.True(reader.IsFinalBlock);
+ Assert.Equal(JsonTokenType.StartObject, reader.TokenType);
+ reader.Read();
+ Assert.Equal(JsonTokenType.PropertyName, reader.TokenType);
+ reader.Read();
+ Assert.Equal(JsonTokenType.StartObject, reader.TokenType);
+ reader.Read();
+ Assert.Equal(JsonTokenType.PropertyName, reader.TokenType);
+ reader.Read();
+ Assert.Equal(JsonTokenType.String, reader.TokenType);
+ reader.Read();
+ Assert.Equal(JsonTokenType.EndObject, reader.TokenType);
+ reader.Read();
+ Assert.Equal(JsonTokenType.EndObject, reader.TokenType);
+ }
+ }
+
+ [Fact]
+ public void Read_WhenReadingSmallJsonPastEnd_Read()
+ {
+ var json = Encoding.UTF8.GetBytes(SmallJson);
+ using (var stream = new MemoryStream(json))
+ using (var reader = new Utf8JsonStreamReader(stream))
+ {
+ Assert.True(reader.IsFinalBlock);
+ Assert.Equal(JsonTokenType.StartObject, reader.TokenType);
+ reader.Read();
+ Assert.Equal(JsonTokenType.PropertyName, reader.TokenType);
+ reader.Read();
+ Assert.Equal(JsonTokenType.StartObject, reader.TokenType);
+ reader.Read();
+ Assert.Equal(JsonTokenType.PropertyName, reader.TokenType);
+ reader.Read();
+ Assert.Equal(JsonTokenType.String, reader.TokenType);
+ reader.Read();
+ Assert.Equal(JsonTokenType.EndObject, reader.TokenType);
+ reader.Read();
+ Assert.Equal(JsonTokenType.EndObject, reader.TokenType);
+ Assert.False(reader.Read());
+ }
+ }
+
+ [Fact]
+ public void Read_WhenReadingWithoutOverflow_Read()
+ {
+ var json = Encoding.UTF8.GetBytes(JsonWithoutOverflow);
+
+ using (var stream = new MemoryStream(json))
+ using (var reader = new Utf8JsonStreamReader(stream))
+ {
+ Assert.Equal(JsonTokenType.StartObject, reader.TokenType);
+ reader.Read();
+ Assert.Equal(JsonTokenType.PropertyName, reader.TokenType);
+ }
+ }
+
+ [Fact]
+ public void Read_WhenReadingWithOverflow_ReadNextBuffer()
+ {
+ var json = Encoding.UTF8.GetBytes(JsonWithOverflowObject);
+ var mock = SetupMockArrayBuffer();
+
+ using (var stream = new MemoryStream(json))
+ using (var reader = new Utf8JsonStreamReader(stream, 1024, mock.Object))
+ {
+ while (reader.Read())
+ {
+ if (reader.TokenType == JsonTokenType.PropertyName && reader.GetString() == "r")
+ {
+ break;
+ }
+ }
+ reader.Read();
+ mock.Verify(m => m.Rent(1024), Times.Exactly(1));
+ Assert.Equal(JsonTokenType.String, reader.TokenType);
+ Assert.Equal("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz", reader.GetString());
+ reader.Read();
+ Assert.Equal(JsonTokenType.PropertyName, reader.TokenType);
+ Assert.Equal("s", reader.GetString());
+ }
+ }
+
+ [Fact]
+ public void Read_WhenReadingWithLargeToken_ResizeBuffer()
+ {
+ var json = Encoding.UTF8.GetBytes("{\"largeToken\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"smallToken\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\"}");
+ var mock = SetupMockArrayBuffer();
+
+ using (var stream = new MemoryStream(json))
+ using (var reader = new Utf8JsonStreamReader(stream, 1024, mock.Object))
+ {
+ reader.Read();
+ reader.Read();
+
+ mock.Verify(m => m.Rent(1024), Times.Exactly(1));
+ mock.Verify(m => m.Rent(2048), Times.Exactly(1));
+ mock.Verify(m => m.Return(It.IsAny(), true), Times.Exactly(1));
+ Assert.Equal(JsonTokenType.String, reader.TokenType);
+ Assert.Equal("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz",
+ reader.GetString());
+ }
+ }
+
+ [Fact]
+ public void Read_WhenReadingWithLargeTokenReadPastFinal()
+ {
+ var json = Encoding.UTF8.GetBytes("{\"largeToken\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\",\"smallToken\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\"}");
+ var mock = SetupMockArrayBuffer();
+
+ using (var stream = new MemoryStream(json))
+ using (var reader = new Utf8JsonStreamReader(stream, 1024, mock.Object))
+ {
+ reader.Read();
+ reader.Read();
+ reader.Read();
+ reader.Read();
+ reader.Read();
+ mock.Verify(m => m.Rent(1024), Times.Exactly(1));
+ mock.Verify(m => m.Rent(2048), Times.Exactly(1));
+ mock.Verify(m => m.Return(It.IsAny(), true), Times.Exactly(1));
+ Assert.False(reader.Read());
+ }
+ }
+
+ [Fact]
+ public void Read_WhenReadingWithOverflowToBufferSize_LoadNextBuffer()
+ {
+ var json = Encoding.UTF8.GetBytes("{\"largeToken\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrst\",\"smallToken\":\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\"}");
+
+ using (var stream = new MemoryStream(json))
+ using (var reader = new Utf8JsonStreamReader(stream, 1024))
+ {
+ reader.Read();
+ reader.Read();
+ reader.Read();
+
+ Assert.Equal(JsonTokenType.PropertyName, reader.TokenType);
+ Assert.Equal("smallToken", reader.GetString());
+ Assert.True(reader.Read());
+ Assert.Equal(JsonTokenType.String, reader.TokenType);
+ Assert.Equal("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz", reader.GetString());
+ }
+ }
+
+ [Fact]
+ public void Dispose_NoErrors()
+ {
+ var json = Encoding.UTF8.GetBytes(SmallJson);
+ var mock = SetupMockArrayBuffer();
+
+ using (var stream = new MemoryStream(json))
+ using (var reader = new Utf8JsonStreamReader(stream, 1024, arrayPool: mock.Object))
+ {
+ Assert.Equal(JsonTokenType.StartObject, reader.TokenType);
+ }
+ mock.Verify(m => m.Return(It.IsAny(), true), Times.Exactly(1));
+ }
+
+ [Fact]
+ public void Dispose_Read_ObjectDisposedException()
+ {
+ var json = Encoding.UTF8.GetBytes(SmallJson);
+ Assert.Throws(() =>
+ {
+ using (var stream = new MemoryStream(json))
+ using (var reader = new Utf8JsonStreamReader(stream))
+ {
+ Assert.Equal(JsonTokenType.StartObject, reader.TokenType);
+ reader.Dispose();
+ reader.Read();
+ }
+ });
+ }
+
+ [Fact]
+ public void Dispose_Skip_ObjectDisposedException()
+ {
+ var json = Encoding.UTF8.GetBytes(SmallJson);
+ Assert.Throws(() =>
+ {
+ using (var stream = new MemoryStream(json))
+ using (var reader = new Utf8JsonStreamReader(stream))
+ {
+ Assert.Equal(JsonTokenType.StartObject, reader.TokenType);
+ reader.Dispose();
+ reader.Skip();
+ }
+ });
+ }
+
+ [Theory]
+ [InlineData("{\"object1\": { \"a\":\"asdad\" }")]
+ [InlineData("{\"object1\": { \"a\":\"asdad }}")]
+ [InlineData("{\"object1\": \"a\":\"asdad\" }}")]
+ public void Skip_WhenReadingWithMalformedJson(string malformedJson)
+ {
+ var json = Encoding.UTF8.GetBytes(malformedJson);
+
+ Assert.ThrowsAny(() =>
+ {
+ using (var stream = new MemoryStream(json))
+ using (var reader = new Utf8JsonStreamReader(stream))
+ {
+ Assert.Equal(JsonTokenType.StartObject, reader.TokenType);
+ reader.Skip();
+ Assert.Equal(JsonTokenType.EndObject, reader.TokenType);
+ }
+ });
+ }
+
+ [Fact]
+ public void Skip_WhenReadingWithoutOverflow_SkipObject()
+ {
+ var json = Encoding.UTF8.GetBytes(JsonWithoutOverflow);
+
+ using (var stream = new MemoryStream(json))
+ using (var reader = new Utf8JsonStreamReader(stream))
+ {
+ Assert.Equal(JsonTokenType.StartObject, reader.TokenType);
+ reader.Skip();
+ Assert.Equal(JsonTokenType.EndObject, reader.TokenType);
+ }
+ }
+
+ [Fact]
+ public void Skip_WhenReadingWithOverflow_Skip()
+ {
+ var json = Encoding.UTF8.GetBytes(JsonWithOverflow);
+ var mock = SetupMockArrayBuffer();
+
+ using (var stream = new MemoryStream(json))
+ using (var reader = new Utf8JsonStreamReader(stream, 1024, mock.Object))
+ {
+ reader.Read();
+ reader.Skip();
+ reader.Read();
+ reader.Skip();
+ Assert.Equal(JsonTokenType.EndObject, reader.TokenType);
+ reader.Read();
+ Assert.Equal("object3", reader.GetString());
+ mock.Verify(m => m.Rent(1024), Times.Exactly(1));
+ }
+ }
+
+ [Fact]
+ public void Skip_WhenReadingWithOverflowObject_ResizeBuffer()
+ {
+ var json = Encoding.UTF8.GetBytes(JsonWithOverflowObject);
+ var mock = SetupMockArrayBuffer();
+
+ using (var stream = new MemoryStream(json))
+ using (var reader = new Utf8JsonStreamReader(stream, 1024, mock.Object))
+ {
+ reader.Read();
+ reader.Skip();
+ reader.Read();
+ Assert.Equal(JsonTokenType.PropertyName, reader.TokenType);
+ Assert.Equal("object2", reader.GetString());
+ mock.Verify(m => m.Rent(1024), Times.Exactly(1));
+ mock.Verify(m => m.Rent(2048), Times.Exactly(1));
+ mock.Verify(m => m.Return(It.IsAny(), true), Times.Exactly(1));
+ }
+ }
+
+ [Fact]
+ public void ReadNextTokenAsString_WhenCalled_AdvanceToken()
+ {
+ var json = Encoding.UTF8.GetBytes("{\"token\":\"value\"}");
+
+ using (var stream = new MemoryStream(json))
+ using (var reader = new Utf8JsonStreamReader(stream))
+ {
+ reader.Read();
+ Assert.Equal(JsonTokenType.PropertyName, reader.TokenType);
+ var result = reader.ReadNextTokenAsString();
+ Assert.Equal(JsonTokenType.String, reader.TokenType);
+ Assert.Equal("value", result);
+ }
+ }
+
+ [Fact]
+ public void ReadNextTokenAsString_WithMalformedJson_GetException()
+ {
+ var json = Encoding.UTF8.GetBytes("{\"token\":\"value}");
+ Assert.ThrowsAny(() =>
+ {
+ using (var stream = new MemoryStream(json))
+ using (var reader = new Utf8JsonStreamReader(stream))
+ {
+ reader.Read();
+ Assert.Equal(JsonTokenType.PropertyName, reader.TokenType);
+ reader.ReadNextTokenAsString();
+ }
+ });
+ }
+
+ [Theory]
+ [InlineData("true", JsonTokenType.True)]
+ [InlineData("false", JsonTokenType.False)]
+ [InlineData("-2", JsonTokenType.Number)]
+ [InlineData("3.14", JsonTokenType.Number)]
+ [InlineData("{}", JsonTokenType.StartObject)]
+ [InlineData("[]", JsonTokenType.StartArray)]
+ [InlineData("[true]", JsonTokenType.StartArray)]
+ [InlineData("[-2]", JsonTokenType.StartArray)]
+ [InlineData("[3.14]", JsonTokenType.StartArray)]
+ [InlineData("[\"a\", \"b\"]", JsonTokenType.StartArray)]
+ public void ReadDelimitedString_WhenValueIsNotString_Throws(string value, JsonTokenType expectedTokenType)
+ {
+ var json = $"{{\"a\":{value}}}";
+ var encodedBytes = Encoding.UTF8.GetBytes(json);
+ var tokenType = JsonTokenType.None;
+ var exceptionThrown = Assert.Throws(() =>
+ {
+ using var stream = new MemoryStream(encodedBytes);
+ using var reader = new Utf8JsonStreamReader(stream);
+ reader.Read();
+ try
+ {
+ reader.ReadDelimitedString();
+ }
+ finally
+ {
+ tokenType = reader.TokenType;
+ }
+ });
+ Assert.NotNull(exceptionThrown.InnerException);
+ Assert.IsType(typeof(InvalidCastException), exceptionThrown.InnerException);
+ Assert.Equal(expectedTokenType, tokenType);
+ }
+
+ [Fact]
+ public void ReadDelimitedString_WhenValueIsString_ReturnsValue()
+ {
+ const string expectedResult = "b";
+ var json = $"{{\"a\":\"{expectedResult}\"}}";
+ var encodedBytes = Encoding.UTF8.GetBytes(json);
+ using (var stream = new MemoryStream(encodedBytes))
+ using (var reader = new Utf8JsonStreamReader(stream))
+ {
+ reader.Read();
+ IEnumerable actualResults = reader.ReadDelimitedString();
+ Assert.Collection(actualResults, actualResult => Assert.Equal(expectedResult, actualResult));
+ Assert.Equal(JsonTokenType.String, reader.TokenType);
+ }
+ }
+
+ [Theory]
+ [InlineData("b,c,d")]
+ [InlineData("b c d")]
+ public void ReadDelimitedString_WhenValueIsDelimitedString_ReturnsValues(string value)
+ {
+ string[] expectedResults = value.Split(new[] { ' ', ',' }, StringSplitOptions.RemoveEmptyEntries);
+ var json = $"{{\"a\":\"{value}\"}}";
+ var encodedBytes = Encoding.UTF8.GetBytes(json);
+ using (var stream = new MemoryStream(encodedBytes))
+ using (var reader = new Utf8JsonStreamReader(stream))
+ {
+ reader.Read();
+ IEnumerable actualResults = reader.ReadDelimitedString();
+ Assert.Equal(expectedResults, actualResults);
+ Assert.Equal(JsonTokenType.String, reader.TokenType);
+ }
+ }
+
+ [Theory]
+ [InlineData("null")]
+ [InlineData("\"b\"")]
+ [InlineData("{}")]
+ public void ReadStringArrayAsIList_WhenValueIsNotArray_ReturnsNull(string value)
+ {
+ var json = $"{{\"a\":{value}}}";
+ var encodedBytes = Encoding.UTF8.GetBytes(json);
+ using (var stream = new MemoryStream(encodedBytes))
+ using (var reader = new Utf8JsonStreamReader(stream))
+ {
+ reader.Read();
+ reader.Read();
+ Assert.NotEqual(JsonTokenType.PropertyName, reader.TokenType);
+ IList actualValues = reader.ReadStringArrayAsIList();
+ Assert.Null(actualValues);
+ }
+ }
+
+ [Fact]
+ public void ReadStringArrayAsIList_WhenValueIsEmptyArray_ReturnsNull()
+ {
+ var encodedBytes = Encoding.UTF8.GetBytes("{\"a\":[]}");
+ using (var stream = new MemoryStream(encodedBytes))
+ using (var reader = new Utf8JsonStreamReader(stream))
+ {
+ reader.Read();
+ reader.Read();
+ Assert.NotEqual(JsonTokenType.PropertyName, reader.TokenType);
+ IList actualValues = reader.ReadStringArrayAsIList();
+ Assert.Null(actualValues);
+ }
+ }
+
+ [Fact]
+ public void ReadStringArrayAsIList_WithSupportedTypes_ReturnsStringArray()
+ {
+ var encodedBytes = Encoding.UTF8.GetBytes("[\"a\",-2,3.14,true,null]");
+ using (var stream = new MemoryStream(encodedBytes))
+ using (var reader = new Utf8JsonStreamReader(stream))
+ {
+ IList actualValues = reader.ReadStringArrayAsIList();
+
+ Assert.Collection(
+ actualValues,
+ actualValue => Assert.Equal("a", actualValue),
+ actualValue => Assert.Equal("-2", actualValue),
+ actualValue => Assert.Equal("3.14", actualValue),
+ actualValue => Assert.Equal("True", actualValue),
+ actualValue => Assert.Equal(null, actualValue));
+ Assert.Equal(JsonTokenType.EndArray, reader.TokenType);
+ }
+ }
+
+ [Theory]
+ [InlineData("[]")]
+ [InlineData("{}")]
+ public void ReadStringArrayAsIList_WithUnsupportedTypes_Throws(string element)
+ {
+ var encodedBytes = Encoding.UTF8.GetBytes($"[{element}]");
+ Assert.Throws(() =>
+ {
+ using (var stream = new MemoryStream(encodedBytes))
+ using (var reader = new Utf8JsonStreamReader(stream))
+ {
+ reader.ReadStringArrayAsIList();
+ }
+ });
+ }
+
+
+ [Theory]
+ [InlineData("true", true)]
+ [InlineData("false", false)]
+ public void ReadNextTokenAsBoolOrFalse_WithValidValues_ReturnsBoolean(string value, bool expectedResult)
+ {
+ var json = $"{{\"a\":{value}}}";
+ var encodedBytes = Encoding.UTF8.GetBytes(json);
+ using (var stream = new MemoryStream(encodedBytes))
+ using (var reader = new Utf8JsonStreamReader(stream))
+ {
+ reader.Read();
+ bool actualResult = reader.ReadNextTokenAsBoolOrFalse();
+ Assert.Equal(expectedResult, actualResult);
+ }
+ }
+
+ [Theory]
+ [InlineData("\"words\"")]
+ [InlineData("-3")]
+ [InlineData("3.3")]
+ [InlineData("[]")]
+ [InlineData("{}")]
+ public void ReadNextTokenAsBoolOrFalse_WithInvalidValues_ReturnsFalse(string value)
+ {
+ var json = $"{{\"a\":{value}}}";
+ var encodedBytes = Encoding.UTF8.GetBytes(json);
+ using (var stream = new MemoryStream(encodedBytes))
+ using (var reader = new Utf8JsonStreamReader(stream))
+ {
+ reader.Read();
+ bool actualResult = reader.ReadNextTokenAsBoolOrFalse();
+ Assert.False(actualResult);
+ }
+ }
+
+ [Fact]
+ public void ReadNextStringOrArrayOfStringsAsReadOnlyList_WhenValueIsNull_ReturnsNull()
+ {
+ const string json = "{\"a\":null}";
+ var encodedBytes = Encoding.UTF8.GetBytes(json);
+ using (var stream = new MemoryStream(encodedBytes))
+ using (var reader = new Utf8JsonStreamReader(stream))
+ {
+ reader.Read();
+ IEnumerable actualResults = reader.ReadNextStringOrArrayOfStringsAsReadOnlyList();
+ Assert.Null(actualResults);
+ Assert.Equal(JsonTokenType.Null, reader.TokenType);
+ }
+ }
+
+ [Theory]
+ [InlineData("true", JsonTokenType.True)]
+ [InlineData("false", JsonTokenType.False)]
+ [InlineData("-2", JsonTokenType.Number)]
+ [InlineData("3.14", JsonTokenType.Number)]
+ [InlineData("{}", JsonTokenType.StartObject)]
+ public void ReadNextStringOrArrayOfStringsAsReadOnlyList_WhenValueIsNotString_ReturnsNull(
+ string value,
+ JsonTokenType expectedTokenType)
+ {
+ var json = $"{{\"a\":{value}}}";
+ var encodedBytes = Encoding.UTF8.GetBytes(json);
+ using (var stream = new MemoryStream(encodedBytes))
+ using (var reader = new Utf8JsonStreamReader(stream))
+ {
+ reader.Read();
+ Assert.Equal(JsonTokenType.PropertyName, reader.TokenType);
+
+ IEnumerable actualResults = reader.ReadNextStringOrArrayOfStringsAsReadOnlyList();
+
+ Assert.Null(actualResults);
+ Assert.Equal(expectedTokenType, reader.TokenType);
+ }
+ }
+
+ [Fact]
+ public void ReadNextStringOrArrayOfStringsAsReadOnlyList_WhenValueIsString_ReturnsValue()
+ {
+ const string expectedResult = "b";
+ var json = $"{{\"a\":\"{expectedResult}\"}}";
+ var encodedBytes = Encoding.UTF8.GetBytes(json);
+ using (var stream = new MemoryStream(encodedBytes))
+ using (var reader = new Utf8JsonStreamReader(stream))
+ {
+ reader.Read();
+ Assert.Equal(JsonTokenType.PropertyName, reader.TokenType);
+
+ IEnumerable actualResults = reader.ReadNextStringOrArrayOfStringsAsReadOnlyList();
+
+ Assert.Collection(actualResults, actualResult => Assert.Equal(expectedResult, actualResult));
+ Assert.Equal(JsonTokenType.String, reader.TokenType);
+ }
+ }
+
+ [Theory]
+ [InlineData("b,c,d")]
+ [InlineData("b c d")]
+ public void ReadNextStringOrArrayOfStringsAsReadOnlyList_WhenValueIsDelimitedString_ReturnsValue(string expectedResult)
+ {
+ var json = $"{{\"a\":\"{expectedResult}\"}}";
+ var encodedBytes = Encoding.UTF8.GetBytes(json);
+ using (var stream = new MemoryStream(encodedBytes))
+ using (var reader = new Utf8JsonStreamReader(stream))
+ {
+ reader.Read();
+ Assert.Equal(JsonTokenType.PropertyName, reader.TokenType);
+
+ IEnumerable actualResults = reader.ReadNextStringOrArrayOfStringsAsReadOnlyList();
+
+ Assert.Collection(actualResults, actualResult => Assert.Equal(expectedResult, actualResult));
+ Assert.Equal(JsonTokenType.String, reader.TokenType);
+ }
+ }
+
+ [Fact]
+ public void ReadNextStringOrArrayOfStringsAsReadOnlyList_WhenValueIsEmptyArray_ReturnsEmptyList()
+ {
+ const string json = "{\"a\":[]}";
+ var encodedBytes = Encoding.UTF8.GetBytes(json);
+ using (var stream = new MemoryStream(encodedBytes))
+ using (var reader = new Utf8JsonStreamReader(stream))
+ {
+ reader.Read();
+ Assert.Equal(JsonTokenType.PropertyName, reader.TokenType);
+
+ IReadOnlyList actualResults = reader.ReadNextStringOrArrayOfStringsAsReadOnlyList();
+
+ Assert.Empty(actualResults);
+ Assert.Equal(JsonTokenType.EndArray, reader.TokenType);
+ }
+ }
+
+ [Theory]
+ [InlineData("null", null)]
+ [InlineData("true", "True")]
+ [InlineData("-2", "-2")]
+ [InlineData("3.14", "3.14")]
+ [InlineData("\"b\"", "b")]
+ public void ReadNextStringOrArrayOfStringsAsReadOnlyList_WhenValueIsConvertibleToString_ReturnsValueAsString(
+ string value,
+ string expectedResult)
+ {
+ var json = $"{{\"a\":[{value}]}}";
+ var encodedBytes = Encoding.UTF8.GetBytes(json);
+ using (var stream = new MemoryStream(encodedBytes))
+ using (var reader = new Utf8JsonStreamReader(stream))
+ {
+ reader.Read();
+
+ Assert.Equal(JsonTokenType.PropertyName, reader.TokenType);
+
+ IEnumerable actualResults = reader.ReadNextStringOrArrayOfStringsAsReadOnlyList();
+
+ Assert.Collection(actualResults, actualResult => Assert.Equal(expectedResult, actualResult));
+ Assert.Equal(JsonTokenType.EndArray, reader.TokenType);
+ }
+ }
+
+ [Theory]
+ [InlineData("[]", JsonTokenType.StartArray)]
+ [InlineData("{}", JsonTokenType.StartObject)]
+ public void ReadNextStringOrArrayOfStringsAsReadOnlyList_WhenValueIsNotConvertibleToString_ReturnsValueAsString(
+ string value,
+ JsonTokenType expectedToken)
+ {
+ var json = $"{{\"a\":[{value}]}}";
+ var encodedBytes = Encoding.UTF8.GetBytes(json);
+ var tokenType = JsonTokenType.None;
+ var exceptionThrown = Assert.Throws(() =>
+ {
+ using var stream = new MemoryStream(encodedBytes);
+ using var reader = new Utf8JsonStreamReader(stream);
+ reader.Read();
+ Assert.Equal(JsonTokenType.PropertyName, reader.TokenType);
+ try
+ {
+ reader.ReadNextStringOrArrayOfStringsAsReadOnlyList();
+ }
+ finally
+ {
+ tokenType = reader.TokenType;
+ }
+ });
+ Assert.Equal(expectedToken, tokenType);
+ }
+
+ [Fact]
+ public void ReadNextStringOrArrayOfStringsAsReadOnlyList_WhenValueIsArrayOfStrings_ReturnsValues()
+ {
+ string[] expectedResults = { "b", "c" };
+ var json = $"{{\"a\":[{string.Join(",", expectedResults.Select(expectedResult => $"\"{expectedResult}\""))}]}}";
+ var encodedBytes = Encoding.UTF8.GetBytes(json);
+ using (var stream = new MemoryStream(encodedBytes))
+ using (var reader = new Utf8JsonStreamReader(stream))
+ {
+ reader.Read();
+ Assert.Equal(JsonTokenType.PropertyName, reader.TokenType);
+
+ IEnumerable actualResults = reader.ReadNextStringOrArrayOfStringsAsReadOnlyList();
+
+ Assert.Equal(expectedResults, actualResults);
+ Assert.Equal(JsonTokenType.EndArray, reader.TokenType);
+ }
+ }
+
+ [Fact]
+ public void ReadStringArrayAsReadOnlyListFromArrayStart_WhenValuesAreConvertibleToString_ReturnsReadOnlyList()
+ {
+ const string json = "[null, true, -2, 3.14, \"a\"]";
+ var encodedBytes = Encoding.UTF8.GetBytes(json);
+ using (var stream = new MemoryStream(encodedBytes))
+ using (var reader = new Utf8JsonStreamReader(stream))
+ {
+ Assert.Equal(JsonTokenType.StartArray, reader.TokenType);
+
+ IEnumerable actualResults = reader.ReadStringArrayAsReadOnlyListFromArrayStart();
+
+ Assert.Collection(
+ actualResults,
+ actualResult => Assert.Equal(null, actualResult),
+ actualResult => Assert.Equal("True", actualResult),
+ actualResult => Assert.Equal("-2", actualResult),
+ actualResult => Assert.Equal("3.14", actualResult),
+ actualResult => Assert.Equal("a", actualResult));
+ Assert.Equal(JsonTokenType.EndArray, reader.TokenType);
+ }
+ }
+
+ [Theory]
+ [InlineData("[]", JsonTokenType.StartArray)]
+ [InlineData("{}", JsonTokenType.StartObject)]
+ public void ReadStringArrayAsReadOnlyListFromArrayStart_WhenValuesAreNotConvertibleToString_Throws(
+ string value,
+ JsonTokenType expectedToken)
+ {
+ var json = $"[{value}]";
+ var encodedBytes = Encoding.UTF8.GetBytes(json);
+ var tokenType = JsonTokenType.None;
+ var exceptionThrown = Assert.Throws(() =>
+ {
+ using var stream = new MemoryStream(encodedBytes);
+ using var reader = new Utf8JsonStreamReader(stream);
+ Assert.Equal(JsonTokenType.StartArray, reader.TokenType);
+ try
+ {
+ reader.ReadStringArrayAsReadOnlyListFromArrayStart();
+ }
+ finally
+ {
+ tokenType = reader.TokenType;
+ }
+ });
+ Assert.Equal(expectedToken, tokenType);
+ }
+
+ private Mock> SetupMockArrayBuffer()
+ {
+ Mock> mock = new Mock>();
+ mock.Setup(m => m.Rent(1024)).Returns(new byte[1024]);
+ mock.Setup(m => m.Rent(2048)).Returns(new byte[2048]);
+ mock.Setup(m => m.Return(It.IsAny(), It.IsAny()));
+
+ return mock;
+ }
+ }
+}