Add Skip and TrySkip APIs to Utf8JsonReader with tests. (dotnet/corefx#37793)
authorAhson Khan <ahkha@microsoft.com>
Tue, 28 May 2019 19:26:08 +0000 (12:26 -0700)
committerGitHub <noreply@github.com>
Tue, 28 May 2019 19:26:08 +0000 (12:26 -0700)
* 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

src/libraries/System.Text.Json/ref/System.Text.Json.cs
src/libraries/System.Text.Json/src/Resources/Strings.resx
src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.cs
src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs
src/libraries/System.Text.Json/tests/JsonTestHelper.cs
src/libraries/System.Text.Json/tests/Utf8JsonReaderTests.MultiSegment.cs
src/libraries/System.Text.Json/tests/Utf8JsonReaderTests.cs

index fd42e29..f778372 100644 (file)
@@ -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<byte> otherUtf8Text) { throw null; }
         public bool TextEquals(System.ReadOnlySpan<char> 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
     {
index 33a2bde..5e254fe 100644 (file)
   <data name="UnexpectedEndOfDataWhileReadingComment" xml:space="preserve">
     <value>Unexpected end of data while reading a comment.</value>
   </data>
+  <data name="CannotSkip" xml:space="preserve">
+    <value>Cannot skip tokens on partial JSON. Either get the whole payload and set _isFinalBlock to true or call TrySkip.</value>
+  </data>
   <data name="JsonSerializerDoesNotSupportComments" xml:space="preserve">
     <value>Comments cannot be stored when deserializing objects, only the Skip and Disallow comment handling modes are supported.</value>
   </data>
index 3529887..4c2d11d 100644 (file)
@@ -132,6 +132,13 @@ namespace System.Text.Json
         public bool HasValueSequence { get; private set; }
 
         /// <summary>
+        /// Returns the mode of this instance of the <see cref="Utf8JsonReader"/>.
+        /// 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.
+        /// </summary>
+        public bool IsFinalBlock => _isFinalBlock;
+
+        /// <summary>
         /// Gets the value of the last processed token as a ReadOnlySpan&lt;byte&gt; slice
         /// of the input payload. If the JSON is provided within a ReadOnlySequence&lt;byte&gt;
         /// and the slice that represents the token value fits in a single segment, then
@@ -261,6 +268,102 @@ namespace System.Text.Json
         }
 
         /// <summary>
+        /// Skips the children of the current JSON token.
+        /// </summary>
+        /// <exception cref="InvalidOperationException">
+        /// Thrown when the reader was given partial data with more data to follow (i.e. _isFinalBlock is false).
+        /// </exception>
+        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);
+            }
+        }
+
+        /// <summary>
+        /// Tries to skip the children of the current JSON token.
+        /// </summary>
+        /// <returns>True if there was enough data for the children to be skipped successfully, else false.</returns>
+        /// <remarks>
+        /// 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.
+        /// </remarks>
+        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;
+        }
+
+        /// <summary>
         /// Compares the UTF-8 encoded text to the unescaped JSON token value in the source and returns true if they match.
         /// </summary>
         /// <param name="otherUtf8Text">The UTF-8 encoded text to compare against.</param>
index ae37339..afc8ba0 100644 (file)
@@ -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));
index fa644da..490e377 100644 (file)
@@ -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<JsonTokenType> GetTokenTypes(string jsonString)
+        {
+            var newtonsoft = new JsonTextReader(new StringReader(jsonString));
+            int totalReads = 0;
+            while (newtonsoft.Read())
+            {
+                totalReads++;
+            }
+
+            var expectedTokenTypes = new List<JsonTokenType>();
+
+            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];
index 13b081a..81ffd23 100644 (file)
@@ -258,6 +258,109 @@ namespace System.Text.Json.Tests
         }
 
         [Theory]
+        [MemberData(nameof(TrySkipValues))]
+        public static void TestTrySkipMultiSegment(string jsonString, JsonTokenType lastToken)
+        {
+            List<JsonTokenType> expectedTokenTypes = JsonTestHelper.GetTokenTypes(jsonString);
+            byte[] dataUtf8 = Encoding.UTF8.GetBytes(jsonString);
+
+            ReadOnlySequence<byte> 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<JsonTokenType> expectedTokenTypesWithoutComments = JsonTestHelper.GetTokenTypes(jsonString);
+
+            jsonString = JsonTestHelper.InsertCommentsEverywhere(jsonString);
+
+            List<JsonTokenType> expectedTokenTypes = JsonTestHelper.GetTokenTypes(jsonString);
+
+            byte[] dataUtf8 = Encoding.UTF8.GetBytes(jsonString);
+
+            ReadOnlySequence<byte> 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<byte> dataUtf8, JsonTokenType lastToken, List<JsonTokenType> 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)
         {
index 7bb7a67..36c8d3a 100644 (file)
@@ -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<JsonTokenType> 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<JsonTokenType> expectedTokenTypesWithoutComments = JsonTestHelper.GetTokenTypes(jsonString);
+
+            jsonString = JsonTestHelper.InsertCommentsEverywhere(jsonString);
+
+            List<JsonTokenType> 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<JsonTokenType> 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<byte> 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<object[]> TrySkipValues
+        {
+            get
+            {
+                return new List<object[]>
+                {
+                    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<object[]> GetCommentTestData
         {
             get