-
Notifications
You must be signed in to change notification settings - Fork 737
Create class for reading Json files in chunks #5530
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d1e4d8a
9ec0869
cfa2169
c1753f9
cc2ad30
32ec713
a9940e9
6f87583
0f75860
5c4269a
c469899
d0f9f5e
d3e6ab8
2d7cba8
4192d9b
f67239b
ca6e1d7
fa9639d
997f199
3e4146c
b403ed8
a1c4844
4ff0f7e
a9884ec
7a467d5
86d3524
c559e69
74b2e54
0c05eb8
a233b40
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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(); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -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 | ||||||||||||||||||
| { | ||||||||||||||||||
| /// <summary> | ||||||||||||||||||
| /// 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 <see cref="Utf8JsonStreamReader"/>, while maintaining a stream and a buffer to read from. | ||||||||||||||||||
| /// </summary> | ||||||||||||||||||
| internal ref struct Utf8JsonStreamReader | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Stream Wrapper: The struct acts as a wrapper around a stream, specifically for the purpose of reading it in chunks rather than loading the entire stream into memory. Utf8JsonReader Wrapper: The struct also wraps the functionality of Utf8JsonReader. This includes functionalities like reading various data types (strings, integers, booleans) from JSON, handling different JSON token types, and managing the state of the JSON reader. How about splitting it into 2 structs, one handles stream buffer, resizing the buffer etc., and another one wraps UTF8JsonReader methods such as GetString(), GetBoolean() etc.,
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would be a large change if we want to do this I would like to do it in a new PR after the last one is complete. |
||||||||||||||||||
| { | ||||||||||||||||||
| 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<byte> _bufferPool; | ||||||||||||||||||
| private int _bufferUsed = 0; | ||||||||||||||||||
|
|
||||||||||||||||||
| internal Utf8JsonStreamReader(Stream stream, int bufferSize = BufferSizeDefault, ArrayPool<byte> arrayPool = null) | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The constructor throws following exception when using (var stream = new MemoryStream(Array.Empty<byte>()))
using (var reader = new Utf8JsonStreamReader(stream))
{
}
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added a test to demonstrate this behavior but I don't think it's worthwhile adding validation for this. |
||||||||||||||||||
| { | ||||||||||||||||||
| 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<byte>.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 | ||||||||||||||||||
jgonz120 marked this conversation as resolved.
Show resolved
Hide resolved
jgonz120 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||
| { | ||||||||||||||||||
| AllowTrailingCommas = true, | ||||||||||||||||||
| CommentHandling = JsonCommentHandling.Skip, | ||||||||||||||||||
| }); | ||||||||||||||||||
|
|
||||||||||||||||||
| ReadStreamIntoBuffer(iniialJsonReaderState); | ||||||||||||||||||
| _reader.Read(); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| internal bool IsFinalBlock => _reader.IsFinalBlock; | ||||||||||||||||||
|
|
||||||||||||||||||
| internal JsonTokenType TokenType => _reader.TokenType; | ||||||||||||||||||
|
|
||||||||||||||||||
| internal bool ValueTextEquals(ReadOnlySpan<byte> utf8Text) => _reader.ValueTextEquals(utf8Text); | ||||||||||||||||||
|
|
||||||||||||||||||
| internal bool TryGetInt32(out int value) => _reader.TryGetInt32(out value); | ||||||||||||||||||
jgonz120 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||
|
|
||||||||||||||||||
| 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(); | ||||||||||||||||||
| } | ||||||||||||||||||
|
Comment on lines
+85
to
+88
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In some cases, which I currently don't understand, the STJ implementation raises an exception when
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we check for the final block first then we won't all the read at all. The exception you're seeing there means that the Utf8JsonReader was told it has all the data but the current property is none. None meaning that there is no JSON data in the reader. If we want to check for the scenario we can, I would think it should be in the constructor though not here. |
||||||||||||||||||
| 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; | ||||||||||||||||||
zivkan marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| internal IList<string> ReadStringArrayAsIList(IList<string> strings = null) | ||||||||||||||||||
jgonz120 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||
| { | ||||||||||||||||||
| if (TokenType == JsonTokenType.StartArray) | ||||||||||||||||||
| { | ||||||||||||||||||
| while (Read() && TokenType != JsonTokenType.EndArray) | ||||||||||||||||||
| { | ||||||||||||||||||
| string value = _reader.ReadTokenAsString(); | ||||||||||||||||||
|
|
||||||||||||||||||
| strings = strings ?? new List<string>(); | ||||||||||||||||||
jgonz120 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||
|
|
||||||||||||||||||
| strings.Add(value); | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| return strings; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| internal IReadOnlyList<string> 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() | ||||||||||||||||||
jgonz120 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||
| { | ||||||||||||||||||
| ThrowExceptionIfDisposed(); | ||||||||||||||||||
|
|
||||||||||||||||||
| if (Read() && (TokenType == JsonTokenType.False || TokenType == JsonTokenType.True)) | ||||||||||||||||||
| { | ||||||||||||||||||
| return GetBoolean(); | ||||||||||||||||||
| } | ||||||||||||||||||
| return false; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| internal IReadOnlyList<string> 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<string> ReadStringArrayAsReadOnlyListFromArrayStart() | ||||||||||||||||||
| { | ||||||||||||||||||
| ThrowExceptionIfDisposed(); | ||||||||||||||||||
|
|
||||||||||||||||||
| List<string> strings = null; | ||||||||||||||||||
|
|
||||||||||||||||||
| while (Read() && _reader.TokenType != JsonTokenType.EndArray) | ||||||||||||||||||
| { | ||||||||||||||||||
| string value = _reader.ReadTokenAsString(); | ||||||||||||||||||
|
|
||||||||||||||||||
| strings = strings ?? new List<string>(); | ||||||||||||||||||
|
|
||||||||||||||||||
| strings.Add(value); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| return (IReadOnlyList<string>)strings ?? Array.Empty<string>(); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| // 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<byte> leftover = oldBuffer.AsSpan((int)_reader.BytesConsumed); | ||||||||||||||||||
jgonz120 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||
| _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); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| /// <summary> | ||||||||||||||||||
| /// Loops through the stream and reads it into the buffer until the buffer is full or the stream is empty, creates the Utf8JsonReader. | ||||||||||||||||||
| /// </summary> | ||||||||||||||||||
| private void ReadStreamIntoBuffer(JsonReaderState jsonReaderState) | ||||||||||||||||||
| { | ||||||||||||||||||
| int bytesRead; | ||||||||||||||||||
| do | ||||||||||||||||||
| { | ||||||||||||||||||
| var spaceLeftInBuffer = _buffer.Length - _bufferUsed; | ||||||||||||||||||
| bytesRead = _stream.Read(_buffer, _bufferUsed, spaceLeftInBuffer); | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about using the Stream.ReadAsync Method instead of the blocking Read method? It would be great if we could use the overload that accepts a Docs state that for an asynchronous version of example, see .NET samples JSON project.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't have links handy, but my understanding is that on .NET Framework at least, files without passing the async flag to the operating system, is that there's a non-trivial perf impact to reading the files with async APIs. Similarly, files opened (from the OS point of view) with the async flag, but then using blocking APIs has a non-trivial perf impact. Also, .NET Framework's async scheduling just isn't nearly as good as .NET 6+'s, so even when async file IO is done consistently, when the delays are low (OS has file cached in memory?) then there's still going to be a measurable perf impact. If this suggestion, using Stream.ReadAsync, is being seriously considered, I'd recommend reaching out to the VS Perf team and checking if my believe about async file IO on .NET Framework is wrong.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Async isn't about making things faster; its about yielding thread pool threads while its blocking, which lets us make sure we're efficiently using the CPU by having only CPU work on them. NuGet during restore spends a lot of time blocking thread pools threads (see Thread Pool watson, its #13, #14, #19, #22 in 17.8), so async where possible should be the approach.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks @davkean good to know. It's a shame that most of the thread pool watson buckets are caused by .NET & Windows APIs where there are no async alternatives. But we should make time to investigate async file reads (where APIs exist). |
||||||||||||||||||
| _bufferUsed += bytesRead; | ||||||||||||||||||
| } | ||||||||||||||||||
| while (bytesRead != 0 && _bufferUsed != _buffer.Length); | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm curious about the need for the do-while loop inside the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I put it in a loop because of Andy's comment here: #5530 (comment). There isn't any guarantee that the stream is actually read to the limit requested. So the loop is necessary to ensure we are reading until the buffer is full. |
||||||||||||||||||
| _reader = new Utf8JsonReader(_buffer.AsSpan(0, _bufferUsed), isFinalBlock: bytesRead == 0, jsonReaderState); | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. https://learn.microsoft.com/dotnet/standard/serialization/system-text-json/use-utf8jsonreader doc suggests to use
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The function is taking in the jsonReaderState because in the constructor the state doesn't already exist. |
||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| 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)); | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
| { | ||
| /// <summary> | ||
| /// An abstract class that defines a function for reading a <see cref="Utf8JsonStreamReader"/> into a <typeparamref name="T"/> | ||
| /// </summary> | ||
| /// <typeparam name="T"></typeparam> | ||
| internal abstract class Utf8JsonStreamReaderConverter<T> | ||
| { | ||
| public abstract T Read(ref Utf8JsonStreamReader reader); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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); | ||
| } | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.