From 17c9d54f25139a5dd2d61e835ec2a93389ef4f73 Mon Sep 17 00:00:00 2001 From: Ahson Khan Date: Tue, 28 May 2019 12:26:08 -0700 Subject: [PATCH] Add Skip and TrySkip APIs to Utf8JsonReader with tests. (dotnet/corefx#37793) * Add Skip and TrySkip APIs to Utf8JsonReader with tests. * Auto-generate the ref assembly. * Remove new lines and update bytes consumed to avoid adjusting OS specific offsets. * Add IsFinalBlock public property. * Break up the skip/try skip method into helpers. * Add more direct tests and update impl to avoid unnecessary struct copy. Commit migrated from https://github.com/dotnet/corefx/commit/2f2aca43ef9d24c794255c3ef5d0238dc479652b --- .../System.Text.Json/ref/System.Text.Json.cs | 3 + .../System.Text.Json/src/Resources/Strings.resx | 3 + .../src/System/Text/Json/Reader/Utf8JsonReader.cs | 103 +++ .../src/System/Text/Json/ThrowHelper.cs | 6 + .../System.Text.Json/tests/JsonTestHelper.cs | 84 ++ .../tests/Utf8JsonReaderTests.MultiSegment.cs | 103 +++ .../System.Text.Json/tests/Utf8JsonReaderTests.cs | 901 ++++++++++++++++++++- 7 files changed, 1202 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Text.Json/ref/System.Text.Json.cs b/src/libraries/System.Text.Json/ref/System.Text.Json.cs index fd42e29..f778372 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs @@ -189,6 +189,7 @@ namespace System.Text.Json public int CurrentDepth { get { throw null; } } public System.Text.Json.JsonReaderState CurrentState { get { throw null; } } public bool HasValueSequence { get { throw null; } } + public bool IsFinalBlock { get { throw null; } } public System.SequencePosition Position { get { throw null; } } public long TokenStartIndex { get { throw null; } } public System.Text.Json.JsonTokenType TokenType { get { throw null; } } @@ -210,6 +211,7 @@ namespace System.Text.Json [System.CLSCompliantAttribute(false)] public ulong GetUInt64() { throw null; } public bool Read() { throw null; } + public void Skip() { } public bool TextEquals(System.ReadOnlySpan otherUtf8Text) { throw null; } public bool TextEquals(System.ReadOnlySpan otherText) { throw null; } public bool TryGetDateTime(out System.DateTime value) { throw null; } @@ -224,6 +226,7 @@ namespace System.Text.Json public bool TryGetUInt32(out uint value) { throw null; } [System.CLSCompliantAttribute(false)] public bool TryGetUInt64(out ulong value) { throw null; } + public bool TrySkip() { throw null; } } public sealed partial class Utf8JsonWriter : System.IDisposable { diff --git a/src/libraries/System.Text.Json/src/Resources/Strings.resx b/src/libraries/System.Text.Json/src/Resources/Strings.resx index 33a2bde..5e254fe 100644 --- a/src/libraries/System.Text.Json/src/Resources/Strings.resx +++ b/src/libraries/System.Text.Json/src/Resources/Strings.resx @@ -362,6 +362,9 @@ Unexpected end of data while reading a comment. + + Cannot skip tokens on partial JSON. Either get the whole payload and set _isFinalBlock to true or call TrySkip. + Comments cannot be stored when deserializing objects, only the Skip and Disallow comment handling modes are supported. diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.cs index 3529887..4c2d11d 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.cs @@ -132,6 +132,13 @@ namespace System.Text.Json public bool HasValueSequence { get; private set; } /// + /// Returns the mode of this instance of the . + /// True when the reader was constructed with the input span containing the entire data to process. + /// False when the reader was constructed knowing that the input span may contain partial data with more data to follow. + /// + public bool IsFinalBlock => _isFinalBlock; + + /// /// Gets the value of the last processed token as a ReadOnlySpan<byte> slice /// of the input payload. If the JSON is provided within a ReadOnlySequence<byte> /// and the slice that represents the token value fits in a single segment, then @@ -261,6 +268,102 @@ namespace System.Text.Json } /// + /// Skips the children of the current JSON token. + /// + /// + /// Thrown when the reader was given partial data with more data to follow (i.e. _isFinalBlock is false). + /// + public void Skip() + { + if (!_isFinalBlock) + { + throw ThrowHelper.GetInvalidOperationException_CannotSkipOnPartial(); + } + + SkipHelper(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SkipHelper() + { + Debug.Assert(_isFinalBlock); + + if (TokenType == JsonTokenType.PropertyName) + { + bool result = Read(); + // Since _isFinalBlock == true here, and the JSON token is not a primitive value or comment. + // Read() is guaranteed to return true OR throw for invalid/incomplete data. + Debug.Assert(result); + } + + if (TokenType == JsonTokenType.StartObject || TokenType == JsonTokenType.StartArray) + { + int depth = CurrentDepth; + do + { + bool result = Read(); + // Since _isFinalBlock == true here, and the JSON token is not a primitive value or comment. + // Read() is guaranteed to return true OR throw for invalid/incomplete data. + Debug.Assert(result); + } + while (depth < CurrentDepth); + } + } + + /// + /// Tries to skip the children of the current JSON token. + /// + /// True if there was enough data for the children to be skipped successfully, else false. + /// + /// If the reader did not have enough data to completely skip the children of the current token, + /// it will be reset to the state it was in before the method was called. + /// + public bool TrySkip() + { + if (_isFinalBlock) + { + SkipHelper(); + return true; + } + + return TrySkipHelper(); + } + + private bool TrySkipHelper() + { + Debug.Assert(!_isFinalBlock); + + Utf8JsonReader restore = this; + + if (TokenType == JsonTokenType.PropertyName) + { + if (!Read()) + { + goto Restore; + } + } + + if (TokenType == JsonTokenType.StartObject || TokenType == JsonTokenType.StartArray) + { + int depth = CurrentDepth; + do + { + if (!Read()) + { + goto Restore; + } + } + while (depth < CurrentDepth); + } + + return true; + + Restore: + this = restore; + return false; + } + + /// /// Compares the UTF-8 encoded text to the unescaped JSON token value in the source and returns true if they match. /// /// The UTF-8 encoded text to compare against. diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs index ae373390c..afc8ba0 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs @@ -193,6 +193,12 @@ namespace System.Text.Json } [MethodImpl(MethodImplOptions.NoInlining)] + public static InvalidOperationException GetInvalidOperationException_CannotSkipOnPartial() + { + return new InvalidOperationException(SR.CannotSkip); + } + + [MethodImpl(MethodImplOptions.NoInlining)] private static InvalidOperationException GetInvalidOperationException(string message, JsonTokenType tokenType) { return new InvalidOperationException(SR.Format(SR.InvalidCast, tokenType, message)); diff --git a/src/libraries/System.Text.Json/tests/JsonTestHelper.cs b/src/libraries/System.Text.Json/tests/JsonTestHelper.cs index fa644da..490e377 100644 --- a/src/libraries/System.Text.Json/tests/JsonTestHelper.cs +++ b/src/libraries/System.Text.Json/tests/JsonTestHelper.cs @@ -260,6 +260,90 @@ namespace System.Text.Json return builder.ToString(); } + private static JsonTokenType MapTokenType(JsonToken token) + { + switch (token) + { + case JsonToken.None: + return JsonTokenType.None; + case JsonToken.StartObject: + return JsonTokenType.StartObject; + case JsonToken.StartArray: + return JsonTokenType.StartArray; + case JsonToken.PropertyName: + return JsonTokenType.PropertyName; + case JsonToken.Comment: + return JsonTokenType.Comment; + case JsonToken.Integer: + case JsonToken.Float: + return JsonTokenType.Number; + case JsonToken.String: + return JsonTokenType.String; + case JsonToken.Boolean: + return JsonTokenType.True; + case JsonToken.Null: + return JsonTokenType.Null; + case JsonToken.EndObject: + return JsonTokenType.EndObject; + case JsonToken.EndArray: + return JsonTokenType.EndArray; + case JsonToken.StartConstructor: + case JsonToken.EndConstructor: + case JsonToken.Date: + case JsonToken.Bytes: + case JsonToken.Undefined: + case JsonToken.Raw: + default: + throw new InvalidOperationException(); + } + } + + public static string InsertCommentsEverywhere(string jsonString) + { + StringBuilder sb = new StringBuilder(); + StringWriter sw = new StringWriter(sb); + + using (JsonWriter writer = new JsonTextWriter(sw)) + { + writer.Formatting = Formatting.Indented; + + var newtonsoft = new JsonTextReader(new StringReader(jsonString)); + writer.WriteComment("comment"); + while (newtonsoft.Read()) + { + writer.WriteToken(newtonsoft, writeChildren: false); + writer.WriteComment("comment"); + } + writer.WriteComment("comment"); + } + return sb.ToString(); + } + + public static List GetTokenTypes(string jsonString) + { + var newtonsoft = new JsonTextReader(new StringReader(jsonString)); + int totalReads = 0; + while (newtonsoft.Read()) + { + totalReads++; + } + + var expectedTokenTypes = new List(); + + for (int i = 0; i < totalReads; i++) + { + newtonsoft = new JsonTextReader(new StringReader(jsonString)); + for (int j = 0; j < i; j++) + { + Assert.True(newtonsoft.Read()); + } + newtonsoft.Skip(); + expectedTokenTypes.Add(MapTokenType(newtonsoft.TokenType)); + } + + return expectedTokenTypes; + } + public static byte[] ReaderLoop(int inpuDataLength, out int length, ref Utf8JsonReader json) { byte[] outputArray = new byte[inpuDataLength]; diff --git a/src/libraries/System.Text.Json/tests/Utf8JsonReaderTests.MultiSegment.cs b/src/libraries/System.Text.Json/tests/Utf8JsonReaderTests.MultiSegment.cs index 13b081a..81ffd23 100644 --- a/src/libraries/System.Text.Json/tests/Utf8JsonReaderTests.MultiSegment.cs +++ b/src/libraries/System.Text.Json/tests/Utf8JsonReaderTests.MultiSegment.cs @@ -258,6 +258,109 @@ namespace System.Text.Json.Tests } [Theory] + [MemberData(nameof(TrySkipValues))] + public static void TestTrySkipMultiSegment(string jsonString, JsonTokenType lastToken) + { + List expectedTokenTypes = JsonTestHelper.GetTokenTypes(jsonString); + byte[] dataUtf8 = Encoding.UTF8.GetBytes(jsonString); + + ReadOnlySequence sequence = JsonTestHelper.CreateSegments(dataUtf8); + TrySkipHelper(sequence, lastToken, expectedTokenTypes, JsonCommentHandling.Disallow); + TrySkipHelper(sequence, lastToken, expectedTokenTypes, JsonCommentHandling.Skip); + TrySkipHelper(sequence, lastToken, expectedTokenTypes, JsonCommentHandling.Allow); + + sequence = JsonTestHelper.GetSequence(dataUtf8, 1); + TrySkipHelper(sequence, lastToken, expectedTokenTypes, JsonCommentHandling.Disallow); + TrySkipHelper(sequence, lastToken, expectedTokenTypes, JsonCommentHandling.Skip); + TrySkipHelper(sequence, lastToken, expectedTokenTypes, JsonCommentHandling.Allow); + } + + [Theory] + [MemberData(nameof(TrySkipValues))] + public static void TestTrySkipWithCommentsMultiSegment(string jsonString, JsonTokenType lastToken) + { + List expectedTokenTypesWithoutComments = JsonTestHelper.GetTokenTypes(jsonString); + + jsonString = JsonTestHelper.InsertCommentsEverywhere(jsonString); + + List expectedTokenTypes = JsonTestHelper.GetTokenTypes(jsonString); + + byte[] dataUtf8 = Encoding.UTF8.GetBytes(jsonString); + + ReadOnlySequence sequence = JsonTestHelper.CreateSegments(dataUtf8); + TrySkipHelper(sequence, JsonTokenType.Comment, expectedTokenTypes, JsonCommentHandling.Allow); + TrySkipHelper(sequence, lastToken, expectedTokenTypesWithoutComments, JsonCommentHandling.Skip); + + sequence = JsonTestHelper.GetSequence(dataUtf8, 1); + TrySkipHelper(sequence, JsonTokenType.Comment, expectedTokenTypes, JsonCommentHandling.Allow); + TrySkipHelper(sequence, lastToken, expectedTokenTypesWithoutComments, JsonCommentHandling.Skip); + } + + private static void TrySkipHelper(ReadOnlySequence dataUtf8, JsonTokenType lastToken, List expectedTokenTypes, JsonCommentHandling commentHandling) + { + var state = new JsonReaderState(new JsonReaderOptions { CommentHandling = commentHandling }); + var json = new Utf8JsonReader(dataUtf8, isFinalBlock: true, state); + + JsonReaderState previous = json.CurrentState; + Assert.Equal(JsonTokenType.None, json.TokenType); + Assert.Equal(0, json.CurrentDepth); + Assert.Equal(0, json.BytesConsumed); + Assert.Equal(false, json.HasValueSequence); + Assert.True(json.ValueSpan.SequenceEqual(default)); + Assert.True(json.ValueSequence.IsEmpty); + + Assert.True(json.TrySkip()); + + JsonReaderState current = json.CurrentState; + Assert.Equal(JsonTokenType.None, json.TokenType); + Assert.Equal(previous, current); + Assert.Equal(0, json.CurrentDepth); + Assert.Equal(0, json.BytesConsumed); + Assert.Equal(false, json.HasValueSequence); + Assert.True(json.ValueSpan.SequenceEqual(default)); + Assert.True(json.ValueSequence.IsEmpty); + + int totalReads = 0; + while (json.Read()) + { + totalReads++; + } + + Assert.Equal(expectedTokenTypes.Count, totalReads); + + previous = json.CurrentState; + Assert.Equal(lastToken, json.TokenType); + Assert.Equal(0, json.CurrentDepth); + Assert.Equal(dataUtf8.Length, json.BytesConsumed); + Assert.Equal(false, json.HasValueSequence); + Assert.True(json.ValueSpan.SequenceEqual(default)); + Assert.True(json.ValueSequence.IsEmpty); + + Assert.True(json.TrySkip()); + + current = json.CurrentState; + Assert.Equal(previous, current); + Assert.Equal(lastToken, json.TokenType); + Assert.Equal(0, json.CurrentDepth); + Assert.Equal(dataUtf8.Length, json.BytesConsumed); + Assert.Equal(false, json.HasValueSequence); + Assert.True(json.ValueSpan.SequenceEqual(default)); + Assert.True(json.ValueSequence.IsEmpty); + + for (int i = 0; i < totalReads; i++) + { + state = new JsonReaderState(new JsonReaderOptions { CommentHandling = commentHandling }); + json = new Utf8JsonReader(dataUtf8, isFinalBlock: true, state); + for (int j = 0; j < i; j++) + { + Assert.True(json.Read()); + } + Assert.True(json.TrySkip()); + Assert.True(expectedTokenTypes[i] == json.TokenType, $"Expected: {expectedTokenTypes[i]}, Actual: {json.TokenType}, , Index: {i}, BytesConsumed: {json.BytesConsumed}"); + } + } + + [Theory] [MemberData(nameof(InvalidJsonStrings))] public static void InvalidJsonMultiSegmentWithEmptyFirst(string jsonString, int expectedlineNumber, int expectedBytePosition, int maxDepth = 64) { diff --git a/src/libraries/System.Text.Json/tests/Utf8JsonReaderTests.cs b/src/libraries/System.Text.Json/tests/Utf8JsonReaderTests.cs index 7bb7a67..36c8d3a 100644 --- a/src/libraries/System.Text.Json/tests/Utf8JsonReaderTests.cs +++ b/src/libraries/System.Text.Json/tests/Utf8JsonReaderTests.cs @@ -25,6 +25,7 @@ namespace System.Text.Json.Tests Assert.Equal(JsonTokenType.None, json.TokenType); Assert.Equal(default, json.Position); Assert.False(json.HasValueSequence); + Assert.False(json.IsFinalBlock); Assert.True(json.ValueSpan.SequenceEqual(default)); Assert.True(json.ValueSequence.IsEmpty); @@ -100,6 +101,7 @@ namespace System.Text.Json.Tests Assert.Equal(JsonTokenType.None, json.TokenType); Assert.Equal(default, json.Position); Assert.False(json.HasValueSequence); + Assert.True(json.IsFinalBlock); Assert.True(json.ValueSpan.SequenceEqual(default)); Assert.True(json.ValueSequence.IsEmpty); @@ -125,6 +127,7 @@ namespace System.Text.Json.Tests Assert.Equal(JsonTokenType.None, json.TokenType); Assert.Equal(default, json.Position); Assert.False(json.HasValueSequence); + Assert.False(json.IsFinalBlock); Assert.True(json.ValueSpan.SequenceEqual(default)); Assert.True(json.ValueSequence.IsEmpty); @@ -143,7 +146,7 @@ namespace System.Text.Json.Tests Assert.Equal(JsonTokenType.Number, json.TokenType); Assert.Equal(default, json.Position); Assert.False(json.HasValueSequence); - Assert.True(json.ValueSpan.SequenceEqual(new byte[] { (byte)'1'})); + Assert.True(json.ValueSpan.SequenceEqual(new byte[] { (byte)'1' })); Assert.True(json.ValueSequence.IsEmpty); Assert.Equal(2, json.CurrentState.BytesConsumed); @@ -162,6 +165,7 @@ namespace System.Text.Json.Tests Assert.Equal(JsonTokenType.Number, json.TokenType); Assert.Equal(default, json.Position); Assert.False(json.HasValueSequence); + Assert.True(json.IsFinalBlock); Assert.True(json.ValueSpan.SequenceEqual(default)); Assert.True(json.ValueSequence.IsEmpty); @@ -486,6 +490,879 @@ namespace System.Text.Json.Tests } [Theory] + [MemberData(nameof(TrySkipValues))] + public static void TestTrySkip(string jsonString, JsonTokenType lastToken) + { + List expectedTokenTypes = JsonTestHelper.GetTokenTypes(jsonString); + TrySkipHelper(jsonString, lastToken, expectedTokenTypes, JsonCommentHandling.Disallow); + TrySkipHelper(jsonString, lastToken, expectedTokenTypes, JsonCommentHandling.Skip); + TrySkipHelper(jsonString, lastToken, expectedTokenTypes, JsonCommentHandling.Allow); + } + + [Theory] + [MemberData(nameof(TrySkipValues))] + public static void TestTrySkipWithComments(string jsonString, JsonTokenType lastToken) + { + List expectedTokenTypesWithoutComments = JsonTestHelper.GetTokenTypes(jsonString); + + jsonString = JsonTestHelper.InsertCommentsEverywhere(jsonString); + + List expectedTokenTypes = JsonTestHelper.GetTokenTypes(jsonString); + TrySkipHelper(jsonString, JsonTokenType.Comment, expectedTokenTypes, JsonCommentHandling.Allow); + TrySkipHelper(jsonString, lastToken, expectedTokenTypesWithoutComments, JsonCommentHandling.Skip); + } + + private static void TrySkipHelper(string jsonString, JsonTokenType lastToken, List expectedTokenTypes, JsonCommentHandling commentHandling) + { + byte[] dataUtf8 = Encoding.UTF8.GetBytes(jsonString); + var state = new JsonReaderState(new JsonReaderOptions { CommentHandling = commentHandling }); + var json = new Utf8JsonReader(dataUtf8, isFinalBlock: true, state); + + JsonReaderState previous = json.CurrentState; + Assert.Equal(JsonTokenType.None, json.TokenType); + Assert.Equal(0, json.CurrentDepth); + Assert.Equal(0, json.BytesConsumed); + Assert.Equal(false, json.HasValueSequence); + Assert.True(json.IsFinalBlock); + Assert.True(json.ValueSpan.SequenceEqual(default)); + Assert.True(json.ValueSequence.IsEmpty); + + Assert.True(json.TrySkip()); + + JsonReaderState current = json.CurrentState; + Assert.Equal(JsonTokenType.None, json.TokenType); + Assert.Equal(previous, current); + Assert.Equal(0, json.CurrentDepth); + Assert.Equal(0, json.BytesConsumed); + Assert.Equal(false, json.HasValueSequence); + Assert.True(json.IsFinalBlock); + Assert.True(json.ValueSpan.SequenceEqual(default)); + Assert.True(json.ValueSequence.IsEmpty); + + json.Skip(); + + current = json.CurrentState; + Assert.Equal(JsonTokenType.None, json.TokenType); + Assert.Equal(previous, current); + Assert.Equal(0, json.CurrentDepth); + Assert.Equal(0, json.BytesConsumed); + Assert.Equal(false, json.HasValueSequence); + Assert.True(json.IsFinalBlock); + Assert.True(json.ValueSpan.SequenceEqual(default)); + Assert.True(json.ValueSequence.IsEmpty); + + int totalReads = 0; + while (json.Read()) + { + totalReads++; + } + + Assert.Equal(expectedTokenTypes.Count, totalReads); + + previous = json.CurrentState; + Assert.Equal(lastToken, json.TokenType); + Assert.Equal(0, json.CurrentDepth); + Assert.Equal(dataUtf8.Length, json.BytesConsumed); + Assert.Equal(false, json.HasValueSequence); + Assert.True(json.IsFinalBlock); + Assert.True(json.ValueSpan.SequenceEqual(default)); + Assert.True(json.ValueSequence.IsEmpty); + + Assert.True(json.TrySkip()); + + current = json.CurrentState; + Assert.Equal(previous, current); + Assert.Equal(lastToken, json.TokenType); + Assert.Equal(0, json.CurrentDepth); + Assert.Equal(dataUtf8.Length, json.BytesConsumed); + Assert.Equal(false, json.HasValueSequence); + Assert.True(json.IsFinalBlock); + Assert.True(json.ValueSpan.SequenceEqual(default)); + Assert.True(json.ValueSequence.IsEmpty); + + json.Skip(); + + current = json.CurrentState; + Assert.Equal(previous, current); + Assert.Equal(lastToken, json.TokenType); + Assert.Equal(0, json.CurrentDepth); + Assert.Equal(dataUtf8.Length, json.BytesConsumed); + Assert.Equal(false, json.HasValueSequence); + Assert.True(json.IsFinalBlock); + Assert.True(json.ValueSpan.SequenceEqual(default)); + Assert.True(json.ValueSequence.IsEmpty); + + for (int i = 0; i < totalReads; i++) + { + state = new JsonReaderState(new JsonReaderOptions { CommentHandling = commentHandling }); + json = new Utf8JsonReader(dataUtf8, isFinalBlock: true, state); + for (int j = 0; j < i; j++) + { + Assert.True(json.Read()); + } + Assert.True(json.TrySkip()); + Assert.True(expectedTokenTypes[i] == json.TokenType, $"Expected: {expectedTokenTypes[i]}, Actual: {json.TokenType}, Index: {i}, BytesConsumed: {json.BytesConsumed}"); + } + + for (int i = 0; i < totalReads; i++) + { + state = new JsonReaderState(new JsonReaderOptions { CommentHandling = commentHandling }); + json = new Utf8JsonReader(dataUtf8, isFinalBlock: true, state); + for (int j = 0; j < i; j++) + { + Assert.True(json.Read()); + } + json.Skip(); + Assert.True(expectedTokenTypes[i] == json.TokenType, $"Expected: {expectedTokenTypes[i]}, Actual: {json.TokenType}, Index: {i}, BytesConsumed: {json.BytesConsumed}"); + } + } + + [Theory] + [InlineData("[]", 1, 2)] + [InlineData("[1, 2, 3, 4, 5]", 1, 15)] + [InlineData("{\"foo\":1}", 2, 8)] + [InlineData("{\"foo\":[1, 2, 3]}", 2, 16)] + public static void BasicSkipTest(string jsonString, int readCount, int expectedConsumed) + { + { + var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(jsonString), isFinalBlock: true, state: default); + for (int i = 0; i < readCount; i++) + { + reader.Read(); + } + + reader.Skip(); + Assert.Equal(expectedConsumed, reader.BytesConsumed); + } + { + var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(jsonString), isFinalBlock: true, state: default); + for (int i = 0; i < readCount; i++) + { + reader.Read(); + } + + Assert.True(reader.TrySkip()); + Assert.Equal(expectedConsumed, reader.BytesConsumed); + } + { + var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(jsonString), isFinalBlock: false, state: default); + for (int i = 0; i < readCount; i++) + { + reader.Read(); + } + + Assert.True(reader.TrySkip()); + Assert.Equal(expectedConsumed, reader.BytesConsumed); + } + } + + [Theory] + [InlineData("[", 1, 1)] + [InlineData("[1, 2, 3, 4, 5", 1, 1)] + [InlineData("{\"foo\":1", 2, 7)] + [InlineData("{\"foo\":[1, 2, 3", 2, 7)] + public static void BasicTrySkipIncomplete(string jsonString, int readCount, int expectedConsumed) + { + { + var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(jsonString), isFinalBlock: true, state: default); + for (int i = 0; i < readCount; i++) + { + reader.Read(); + } + + try + { + reader.Skip(); + Assert.True(false, "Expected JsonException was not thrown for incomplete JSON payload when skipping."); + } + catch (JsonException) { } + } + { + var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(jsonString), isFinalBlock: true, state: default); + for (int i = 0; i < readCount; i++) + { + reader.Read(); + } + + try + { + reader.TrySkip(); + Assert.True(false, "Expected JsonException was not thrown for incomplete JSON payload when skipping."); + } + catch (JsonException) { } + } + { + var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(jsonString), isFinalBlock: false, state: default); + for (int i = 0; i < readCount; i++) + { + reader.Read(); + } + + Assert.False(reader.TrySkip()); + Assert.Equal(expectedConsumed, reader.BytesConsumed); + } + } + + [Fact] + public static void TrySkipOnValuePartialWithWhiteSpace() + { + string jsonString = "[ 1, "; + + { + var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(jsonString), isFinalBlock: true, state: default); + reader.Read(); + reader.Read(); + Assert.Equal(3, reader.BytesConsumed); + Assert.True(reader.TrySkip()); + Assert.Equal(3, reader.BytesConsumed); + + try + { + reader.Read(); + Assert.True(false, "Expected JsonException was not thrown for incomplete JSON payload when reading."); + } + catch (JsonException) { } + + Assert.Equal(5, reader.BytesConsumed); // After exception, state is not restored. + } + { + var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(jsonString), isFinalBlock: true, state: default); + reader.Read(); + Assert.Equal(1, reader.BytesConsumed); + + try + { + reader.TrySkip(); + Assert.True(false, "Expected JsonException was not thrown for incomplete JSON payload when skipping."); + } + catch (JsonException) { } + + Assert.Equal(5, reader.BytesConsumed); // After exception, state is not restored. + } + { + var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(jsonString), isFinalBlock: false, state: default); + reader.Read(); + Assert.Equal(1, reader.BytesConsumed); + Assert.False(reader.TrySkip()); + Assert.Equal(1, reader.BytesConsumed); + } + } + + [Fact] + public static void TrySkipPartialAndContinueWithWhiteSpace() + { + string jsonString = "[ 1, 2]"; + byte[] utf8Data = Encoding.UTF8.GetBytes(jsonString); + + // "[ 1, " + var reader = new Utf8JsonReader(utf8Data.AsSpan(0, 5), isFinalBlock: false, state: default); + reader.Read(); + Assert.Equal(1, reader.BytesConsumed); + Assert.False(reader.TrySkip()); + Assert.Equal(1, reader.BytesConsumed); + + // " 1, 2]" + int previousConsumed = (int)reader.BytesConsumed; + reader = new Utf8JsonReader(utf8Data.AsSpan(previousConsumed), isFinalBlock: true, reader.CurrentState); + Assert.True(reader.TrySkip()); + Assert.Equal(utf8Data.Length - previousConsumed, reader.BytesConsumed); + } + + [Theory] + [MemberData(nameof(TrySkipValues))] + public static void TestTrySkipPartial(string jsonString, JsonTokenType lastToken) + { + byte[] dataUtf8 = Encoding.UTF8.GetBytes(jsonString); + + // Skip, then skip some more + for (int i = 0; i < dataUtf8.Length; i++) + { + JsonReaderState state = default; + var json = new Utf8JsonReader(dataUtf8.AsSpan(0, i), isFinalBlock: false, state); + + long bytesConsumed = json.BytesConsumed; + while (json.TrySkip()) + { + Assert.True(json.TokenType != JsonTokenType.PropertyName && json.TokenType != JsonTokenType.StartObject && json.TokenType != JsonTokenType.StartArray); + if (bytesConsumed == json.BytesConsumed) + { + if (!json.Read()) + { + break; + } + } + else + { + bytesConsumed = json.BytesConsumed; + } + } + + ValidateNextTrySkip(ref json); + + long consumed = json.BytesConsumed; + Assert.Equal(consumed, json.CurrentState.BytesConsumed); + + json = new Utf8JsonReader(dataUtf8.AsSpan((int)consumed), isFinalBlock: true, json.CurrentState); + while (json.TrySkip()) + { + Assert.True(json.TokenType != JsonTokenType.PropertyName && json.TokenType != JsonTokenType.StartObject && json.TokenType != JsonTokenType.StartArray); + if (bytesConsumed == json.BytesConsumed) + { + if (!json.Read()) + { + break; + } + } + else + { + bytesConsumed = json.BytesConsumed; + } + } + Assert.Equal(dataUtf8.Length - consumed, json.BytesConsumed); + } + + // Read, then skip + for (int i = 0; i < dataUtf8.Length; i++) + { + JsonReaderState state = default; + var json = new Utf8JsonReader(dataUtf8.AsSpan(0, i), isFinalBlock: false, state); + while (json.Read()) + ; + + ValidateNextTrySkip(ref json); + + long consumed = json.BytesConsumed; + Assert.Equal(consumed, json.CurrentState.BytesConsumed); + + json = new Utf8JsonReader(dataUtf8.AsSpan((int)consumed), isFinalBlock: true, json.CurrentState); + long bytesConsumed = json.BytesConsumed; + while (json.TrySkip()) + { + Assert.True(json.TokenType != JsonTokenType.PropertyName && json.TokenType != JsonTokenType.StartObject && json.TokenType != JsonTokenType.StartArray); + if (bytesConsumed == json.BytesConsumed) + { + if (!json.Read()) + { + break; + } + } + else + { + bytesConsumed = json.BytesConsumed; + } + } + Assert.Equal(dataUtf8.Length - consumed, json.BytesConsumed); + } + + // Skip, then read + for (int i = 0; i < dataUtf8.Length; i++) + { + JsonReaderState state = default; + var json = new Utf8JsonReader(dataUtf8.AsSpan(0, i), isFinalBlock: false, state); + long bytesConsumed = json.BytesConsumed; + while (json.TrySkip()) + { + Assert.True(json.TokenType != JsonTokenType.PropertyName && json.TokenType != JsonTokenType.StartObject && json.TokenType != JsonTokenType.StartArray); + if (bytesConsumed == json.BytesConsumed) + { + if (!json.Read()) + { + break; + } + } + else + { + bytesConsumed = json.BytesConsumed; + } + } + + ValidateNextTrySkip(ref json); + + long consumed = json.BytesConsumed; + Assert.Equal(consumed, json.CurrentState.BytesConsumed); + + json = new Utf8JsonReader(dataUtf8.AsSpan((int)consumed), isFinalBlock: true, json.CurrentState); + while (json.Read()) + ; + Assert.Equal(dataUtf8.Length - consumed, json.BytesConsumed); + Assert.Equal(lastToken, json.TokenType); + } + } + + private static void ValidateNextTrySkip(ref Utf8JsonReader json) + { + JsonReaderState previous = json.CurrentState; + JsonTokenType prevTokenType = json.TokenType; + int prevDepth = json.CurrentDepth; + long prevConsumed = json.BytesConsumed; + Assert.Equal(false, json.HasValueSequence); + Assert.True(json.ValueSequence.IsEmpty); + ReadOnlySpan prevValue = json.ValueSpan; + + if (json.TokenType == JsonTokenType.PropertyName || json.TokenType == JsonTokenType.StartObject || json.TokenType == JsonTokenType.StartArray) + { + Assert.False(json.TrySkip()); + } + else + { + Assert.True(json.TrySkip()); + } + + JsonReaderState current = json.CurrentState; + Assert.Equal(previous, current); + Assert.Equal(prevTokenType, json.TokenType); + Assert.Equal(prevDepth, json.CurrentDepth); + Assert.Equal(prevConsumed, json.BytesConsumed); + Assert.Equal(false, json.HasValueSequence); + Assert.True(json.ValueSequence.IsEmpty); + Assert.True(json.ValueSpan.SequenceEqual(prevValue)); + } + + [Theory] + [MemberData(nameof(TrySkipValues))] + public static void TestSkipPartial(string jsonString, JsonTokenType lastToken) + { + byte[] dataUtf8 = Encoding.UTF8.GetBytes(jsonString); + + var json = new Utf8JsonReader(dataUtf8, isFinalBlock: false, default); + try + { + Assert.False(json.IsFinalBlock); + json.Skip(); + Assert.True(false, "Expected InvalidOperationException was not thrown when calling Skip with isFinalBlock = false, even if whole payload is available."); + } + catch (InvalidOperationException) { } + + // Skip, then skip some more + for (int i = 0; i < dataUtf8.Length; i++) + { + JsonReaderState state = default; + json = new Utf8JsonReader(dataUtf8.AsSpan(0, i), isFinalBlock: false, state); + + try + { + Assert.False(json.IsFinalBlock); + json.Skip(); + Assert.True(false, "Expected InvalidOperationException was not thrown when calling Skip with isFinalBlock = false"); + } + catch (InvalidOperationException) { } + + long bytesConsumed = json.BytesConsumed; + while (json.TrySkip()) + { + Assert.True(json.TokenType != JsonTokenType.PropertyName && json.TokenType != JsonTokenType.StartObject && json.TokenType != JsonTokenType.StartArray); + if (bytesConsumed == json.BytesConsumed) + { + if (!json.Read()) + { + break; + } + } + else + { + bytesConsumed = json.BytesConsumed; + } + } + + long consumed = json.BytesConsumed; + Assert.Equal(consumed, json.CurrentState.BytesConsumed); + + json = new Utf8JsonReader(dataUtf8.AsSpan((int)consumed), isFinalBlock: true, json.CurrentState); + while (true) + { + Assert.True(json.IsFinalBlock); + json.Skip(); + Assert.True(json.TokenType != JsonTokenType.PropertyName && json.TokenType != JsonTokenType.StartObject && json.TokenType != JsonTokenType.StartArray); + if (bytesConsumed == json.BytesConsumed) + { + if (!json.Read()) + { + break; + } + } + else + { + bytesConsumed = json.BytesConsumed; + } + } + Assert.Equal(dataUtf8.Length - consumed, json.BytesConsumed); + } + + // Read, then skip + for (int i = 0; i < dataUtf8.Length; i++) + { + JsonReaderState state = default; + json = new Utf8JsonReader(dataUtf8.AsSpan(0, i), isFinalBlock: false, state); + while (json.Read()) + ; + + long consumed = json.BytesConsumed; + Assert.Equal(consumed, json.CurrentState.BytesConsumed); + + json = new Utf8JsonReader(dataUtf8.AsSpan((int)consumed), isFinalBlock: true, json.CurrentState); + long bytesConsumed = json.BytesConsumed; + while (true) + { + Assert.True(json.IsFinalBlock); + json.Skip(); + Assert.True(json.TokenType != JsonTokenType.PropertyName && json.TokenType != JsonTokenType.StartObject && json.TokenType != JsonTokenType.StartArray); + if (bytesConsumed == json.BytesConsumed) + { + if (!json.Read()) + { + break; + } + } + else + { + bytesConsumed = json.BytesConsumed; + } + } + Assert.Equal(dataUtf8.Length - consumed, json.BytesConsumed); + } + } + + [Fact] + public static void SkipInvalid() + { + string jsonString = "[[[]], {\"a\":1, \"b\": 2}, 3, {\"a\":{}, {\"c\":[]} }, [{\"a\":1, \"b\": 2}, null]"; + byte[] dataUtf8 = Encoding.UTF8.GetBytes(jsonString); + + { + var json = new Utf8JsonReader(dataUtf8, isFinalBlock: true, default); + Assert.True(json.Read()); + Assert.Equal(JsonTokenType.StartArray, json.TokenType); + try + { + Assert.True(json.IsFinalBlock); + json.Skip(); + Assert.True(false, "Expected JsonException was not thrown for invalid JSON payload when skipping."); + } + catch (JsonException) { } + } + + { + var json = new Utf8JsonReader(dataUtf8, isFinalBlock: true, default); + Assert.True(json.Read()); + Assert.Equal(JsonTokenType.StartArray, json.TokenType); + try + { + Assert.True(json.IsFinalBlock); + json.TrySkip(); + Assert.True(false, "Expected JsonException was not thrown for invalid JSON payload when skipping."); + } + catch (JsonException) { } + } + + { + var json = new Utf8JsonReader(dataUtf8, isFinalBlock: false, default); + Assert.True(json.Read()); + Assert.Equal(JsonTokenType.StartArray, json.TokenType); + try + { + Assert.False(json.IsFinalBlock); + json.TrySkip(); + Assert.True(false, "Expected JsonException was not thrown for invalid JSON payload when skipping."); + } + catch (JsonException) { } + } + } + + [Theory] + [InlineData("[[", 1, JsonTokenType.StartArray)] + [InlineData("[[", 2, JsonTokenType.StartArray)] + [InlineData("[[//comm", 2, JsonTokenType.StartArray)] + [InlineData("[[[]], {\"a\":1, \"b\": 2}, 3, {\"a\":{}, \"b\":{\"c\":[]} }, [{\"a\":1, \"b\": 2}, null]", 1, JsonTokenType.StartArray)] + [InlineData("[[[]], {\"a\":", 7, JsonTokenType.PropertyName)] + public static void SkipIncomplete(string jsonString, int numReads, JsonTokenType skipTokenType) + { + byte[] dataUtf8 = Encoding.UTF8.GetBytes(jsonString); + + { + var state = new JsonReaderState(new JsonReaderOptions { CommentHandling = JsonCommentHandling.Allow }); + var json = new Utf8JsonReader(dataUtf8, isFinalBlock: true, state); + for (int i = 0; i < numReads; i++) + { + Assert.True(json.Read()); + } + Assert.Equal(skipTokenType, json.TokenType); + try + { + Assert.True(json.IsFinalBlock); + json.Skip(); + Assert.True(false, "Expected JsonException was not thrown for incomplete/invalid JSON payload when skipping."); + } + catch (JsonException) { } + } + + { + var state = new JsonReaderState(new JsonReaderOptions { CommentHandling = JsonCommentHandling.Allow }); + var json = new Utf8JsonReader(dataUtf8, isFinalBlock: true, state); + for (int i = 0; i < numReads; i++) + { + Assert.True(json.Read()); + } + Assert.Equal(skipTokenType, json.TokenType); + try + { + Assert.True(json.IsFinalBlock); + json.TrySkip(); + Assert.True(false, "Expected JsonException was not thrown for incomplete/invalid JSON payload when skipping."); + } + catch (JsonException) { } + } + + { + var state = new JsonReaderState(new JsonReaderOptions { CommentHandling = JsonCommentHandling.Allow }); + var json = new Utf8JsonReader(dataUtf8, isFinalBlock: false, state); + for (int i = 0; i < numReads; i++) + { + Assert.True(json.Read()); + } + Assert.Equal(skipTokenType, json.TokenType); + long before = json.BytesConsumed; + Assert.False(json.IsFinalBlock); + Assert.False(json.TrySkip()); + Assert.Equal(skipTokenType, json.TokenType); + Assert.Equal(before, json.BytesConsumed); + } + } + + [Fact] + public static void SkipAtCommentDoesNothing() + { + string jsonString = "[//some comment\n 1, 2, 3]"; + byte[] dataUtf8 = Encoding.UTF8.GetBytes(jsonString); + + var state = new JsonReaderState(new JsonReaderOptions { CommentHandling = JsonCommentHandling.Allow }); + var json = new Utf8JsonReader(dataUtf8, isFinalBlock: true, state); + Assert.True(json.Read()); + Assert.True(json.Read()); + Assert.Equal(JsonTokenType.Comment, json.TokenType); + + json.Skip(); + + Assert.Equal(JsonTokenType.Comment, json.TokenType); + //Assert.Equal("some comment", json.GetComment()); TODO: https://github.com/dotnet/corefx/issues/33347 + + Assert.True(json.Read()); + Assert.Equal(JsonTokenType.Number, json.TokenType); + Assert.Equal(1, json.GetInt32()); + + json = new Utf8JsonReader(dataUtf8, isFinalBlock: true, state); + Assert.True(json.Read()); + Assert.True(json.Read()); + Assert.Equal(JsonTokenType.Comment, json.TokenType); + + Assert.True(json.TrySkip()); + + Assert.Equal(JsonTokenType.Comment, json.TokenType); + //Assert.Equal("some comment", json.GetComment()); TODO: https://github.com/dotnet/corefx/issues/33347 + + Assert.True(json.Read()); + Assert.Equal(JsonTokenType.Number, json.TokenType); + Assert.Equal(1, json.GetInt32()); + } + + [Fact] + public static void SkipTest() + { + string jsonString = @"{""propertyName"": {""foo"": ""bar""},""nestedArray"": {""numbers"": [1,2,3]}}"; + + byte[] dataUtf8 = Encoding.UTF8.GetBytes(jsonString); + + { + var json = new Utf8JsonReader(dataUtf8, isFinalBlock: true, state: default); + json.Skip(); + Assert.Equal(0, json.CurrentDepth); + Assert.Equal(JsonTokenType.None, json.TokenType); + Assert.Equal(0, json.BytesConsumed); + } + + { + var json = new Utf8JsonReader(dataUtf8, isFinalBlock: true, state: default); + // start object + Assert.True(json.Read()); + Assert.Equal(JsonTokenType.StartObject, json.TokenType); + Assert.Equal(0, json.CurrentDepth); + json.Skip(); + Assert.Equal(0, json.CurrentDepth); + Assert.Equal(JsonTokenType.EndObject, json.TokenType); + Assert.Equal(dataUtf8.Length, json.BytesConsumed); + } + + { + var json = new Utf8JsonReader(dataUtf8, isFinalBlock: true, state: default); + // start object, property + Assert.True(json.Read()); + Assert.True(json.Read()); + Assert.Equal(JsonTokenType.PropertyName, json.TokenType); + Assert.Equal(1, json.CurrentDepth); + json.Skip(); + Assert.Equal(1, json.CurrentDepth); + Assert.Equal(JsonTokenType.EndObject, json.TokenType); + Assert.Equal(31, json.BytesConsumed); + } + + { + var json = new Utf8JsonReader(dataUtf8, isFinalBlock: true, state: default); + // start object, property, start object + Assert.True(json.Read()); + Assert.True(json.Read()); + Assert.True(json.Read()); + Assert.Equal(JsonTokenType.StartObject, json.TokenType); + Assert.Equal(1, json.CurrentDepth); + json.Skip(); + Assert.Equal(1, json.CurrentDepth); + Assert.Equal(JsonTokenType.EndObject, json.TokenType); + Assert.Equal(31, json.BytesConsumed); + } + + { + var json = new Utf8JsonReader(dataUtf8, isFinalBlock: true, state: default); + for (int i = 0; i < 4; i++) + { + // start object, property, start object, property + Assert.True(json.Read()); + } + Assert.Equal(JsonTokenType.PropertyName, json.TokenType); + Assert.Equal(2, json.CurrentDepth); + json.Skip(); + Assert.Equal(2, json.CurrentDepth); + Assert.Equal(JsonTokenType.String, json.TokenType); + Assert.Equal(30, json.BytesConsumed); + } + + { + var json = new Utf8JsonReader(dataUtf8, isFinalBlock: true, state: default); + for (int i = 0; i < 5; i++) + { + // start object, property, start object, property, string value + Assert.True(json.Read()); + } + Assert.Equal(JsonTokenType.String, json.TokenType); + Assert.Equal(2, json.CurrentDepth); + json.Skip(); + Assert.Equal(2, json.CurrentDepth); + Assert.Equal(JsonTokenType.String, json.TokenType); + Assert.Equal(30, json.BytesConsumed); + } + + { + var json = new Utf8JsonReader(dataUtf8, isFinalBlock: true, state: default); + for (int i = 0; i < 6; i++) + { + // start object, property, start object, property, string value, end object + Assert.True(json.Read()); + } + Assert.Equal(JsonTokenType.EndObject, json.TokenType); + Assert.Equal(1, json.CurrentDepth); + json.Skip(); + Assert.Equal(1, json.CurrentDepth); + Assert.Equal(JsonTokenType.EndObject, json.TokenType); + Assert.Equal(31, json.BytesConsumed); + } + + { + var json = new Utf8JsonReader(dataUtf8, isFinalBlock: true, state: default); + for (int i = 0; i < 9; i++) + { + // start object, property, start object, property, string value, property, start object, property + Assert.True(json.Read()); + } + Assert.Equal(JsonTokenType.PropertyName, json.TokenType); + Assert.Equal(2, json.CurrentDepth); + json.Skip(); + Assert.Equal(2, json.CurrentDepth); + Assert.Equal(JsonTokenType.EndArray, json.TokenType); + Assert.Equal(66, json.BytesConsumed); + } + + { + var json = new Utf8JsonReader(dataUtf8, isFinalBlock: true, state: default); + for (int i = 0; i < 10; i++) + { + // start object, property, start object, property, string value, property, start object, property, start array + Assert.True(json.Read()); + } + Assert.Equal(JsonTokenType.StartArray, json.TokenType); + Assert.Equal(2, json.CurrentDepth); + json.Skip(); + Assert.Equal(2, json.CurrentDepth); + Assert.Equal(JsonTokenType.EndArray, json.TokenType); + Assert.Equal(66, json.BytesConsumed); + } + + { + var json = new Utf8JsonReader(dataUtf8, isFinalBlock: true, state: default); + for (int i = 0; i < 11; i++) + { + // start object, property, start object, property, string value, property, start object, property, start array, number value + Assert.True(json.Read()); + } + Assert.Equal(JsonTokenType.Number, json.TokenType); + Assert.Equal(3, json.CurrentDepth); + json.Skip(); + Assert.Equal(3, json.CurrentDepth); + Assert.Equal(JsonTokenType.Number, json.TokenType); + Assert.Equal(61, json.BytesConsumed); + } + } + + [Fact] + public static void SkipTestEmpty() + { + string jsonString = @"{""nestedArray"": {""empty"": [],""empty"": [{}]}}"; + + byte[] dataUtf8 = Encoding.UTF8.GetBytes(jsonString); + + { + var json = new Utf8JsonReader(dataUtf8, isFinalBlock: true, state: default); + for (int i = 0; i < 4; i++) + { + // start object, property, start object, property + Assert.True(json.Read()); + } + Assert.Equal(JsonTokenType.PropertyName, json.TokenType); + Assert.Equal(2, json.CurrentDepth); + json.Skip(); + Assert.Equal(2, json.CurrentDepth); + Assert.Equal(JsonTokenType.EndArray, json.TokenType); + Assert.Equal(28, json.BytesConsumed); + } + + { + var json = new Utf8JsonReader(dataUtf8, isFinalBlock: true, state: default); + for (int i = 0; i < 5; i++) + { + // start object, property, start object, property, start array + Assert.True(json.Read()); + } + Assert.Equal(JsonTokenType.StartArray, json.TokenType); + Assert.Equal(2, json.CurrentDepth); + json.Skip(); + Assert.Equal(2, json.CurrentDepth); + Assert.Equal(JsonTokenType.EndArray, json.TokenType); + Assert.Equal(28, json.BytesConsumed); + } + + { + var json = new Utf8JsonReader(dataUtf8, isFinalBlock: true, state: default); + for (int i = 0; i < 7; i++) + { + // start object, property, start object, property, start array, end array, property name + Assert.True(json.Read()); + } + Assert.Equal(JsonTokenType.PropertyName, json.TokenType); + Assert.Equal(2, json.CurrentDepth); + json.Skip(); + Assert.Equal(2, json.CurrentDepth); + Assert.Equal(JsonTokenType.EndArray, json.TokenType); + Assert.Equal(42, json.BytesConsumed); + } + } + + [Theory] // Pad depth by nested objects, but minimize the text [InlineData(1, true, true)] [InlineData(2, true, true)] @@ -3334,6 +4211,28 @@ namespace System.Text.Json.Tests } } + public static IEnumerable TrySkipValues + { + get + { + return new List + { + new object[] {"[[[]], {\"a\":1, \"b\": 2}, 3, {\"a\":{}, \"b\":{\"c\":[]} }, [{\"a\":1, \"b\": 2}, null]]", JsonTokenType.EndArray}, + new object[] {"[]", JsonTokenType.EndArray}, + new object[] {"[[],[],[],[]]", JsonTokenType.EndArray}, + new object[] {"[[[],[],[]]]", JsonTokenType.EndArray}, + new object[] {"[{},{},{},{}]", JsonTokenType.EndArray}, + new object[] {"[{\"a\":[], \"b\":[], \"c\":[]}]", JsonTokenType.EndArray}, + new object[] {"{\"a\":{\"b\":{}}, \"c\":[1, 2], \"d\": 3, \"e\":[[], [{}] ], \"f\":{\"g\":[1, 2], \"e\":null}}", JsonTokenType.EndObject}, + new object[] {"{}", JsonTokenType.EndObject}, + new object[] {"{\"a\":{}, \"b\":{}, \"c\":{}, \"d\":{}}", JsonTokenType.EndObject}, + new object[] {"{\"a\":{\"b\":{}, \"c\":{}, \"d\":{}}}", JsonTokenType.EndObject}, + new object[] {"{\"a\":[], \"b\":[], \"c\":[], \"d\":[]}", JsonTokenType.EndObject}, + new object[] {"{\"a\":[{}, {}, {}]}", JsonTokenType.EndObject}, + }; + } + } + public static IEnumerable GetCommentTestData { get -- 2.7.4