From 4f6ea535a345a7ab59c00b6d427bbb2737055b33 Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Mon, 19 Oct 2020 15:14:51 -0500 Subject: [PATCH 1/2] Add public JsonElement.ParseValue() and TryParseValue() --- .../System.Text.Json/ref/System.Text.Json.cs | 2 + .../src/System.Text.Json.csproj | 1 + .../Text/Json/Document/JsonDocument.Parse.cs | 3 +- .../Text/Json/Document/JsonElement.Parse.cs | 100 ++++++++++++ .../System/Text/Json/Document/JsonElement.cs | 11 -- .../tests/JsonElementReadTests.cs | 145 ++++++++++++++++++ .../tests/System.Text.Json.Tests.csproj | 1 + 7 files changed, 251 insertions(+), 12 deletions(-) create mode 100644 src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.Parse.cs create mode 100644 src/libraries/System.Text.Json/tests/JsonElementReadTests.cs 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 14b39550ece3c0..a70874667f20f9 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs @@ -70,6 +70,7 @@ public readonly partial struct JsonElement [System.CLSCompliantAttribute(false)] public ulong GetUInt64() { throw null; } public override string? ToString() { throw null; } + public static System.Text.Json.JsonElement ParseValue(ref System.Text.Json.Utf8JsonReader reader) { throw null; } public bool TryGetByte(out byte value) { throw null; } public bool TryGetBytesFromBase64([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out byte[]? value) { throw null; } public bool TryGetDateTime(out System.DateTime value) { throw null; } @@ -92,6 +93,7 @@ public readonly partial struct JsonElement public bool TryGetUInt32(out uint value) { throw null; } [System.CLSCompliantAttribute(false)] public bool TryGetUInt64(out ulong value) { throw null; } + public static bool TryParseValue(ref System.Text.Json.Utf8JsonReader reader, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out System.Text.Json.JsonElement? element) { throw null; } public bool ValueEquals(System.ReadOnlySpan utf8Text) { throw null; } public bool ValueEquals(System.ReadOnlySpan text) { throw null; } public bool ValueEquals(string? text) { throw null; } diff --git a/src/libraries/System.Text.Json/src/System.Text.Json.csproj b/src/libraries/System.Text.Json/src/System.Text.Json.csproj index cc000f05affa1a..81242494860ea9 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -33,6 +33,7 @@ + diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.Parse.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.Parse.cs index 0339ac4e50f9e9..e81c52ea548dfa 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.Parse.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.Parse.cs @@ -337,7 +337,8 @@ public static JsonDocument ParseValue(ref Utf8JsonReader reader) bool ret = TryParseValue(ref reader, out JsonDocument? document, shouldThrow: true, useArrayPools: true); Debug.Assert(ret, "TryParseValue returned false with shouldThrow: true."); - return document!; + Debug.Assert(document != null, "null document returned with shouldThrow: true."); + return document; } internal static bool TryParseValue( diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.Parse.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.Parse.cs new file mode 100644 index 00000000000000..ccbabec49bbe6f --- /dev/null +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.Parse.cs @@ -0,0 +1,100 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +namespace System.Text.Json +{ + public readonly partial struct JsonElement + { + /// + /// Parses one JSON value (including objects or arrays) from the provided reader. + /// + /// The reader to read. + /// + /// A JsonDocument representing the value (and nested values) read from the reader. + /// + /// + /// + /// If the property of + /// is or , the + /// reader will be advanced by one call to to determine + /// the start of the value. + /// + /// + /// + /// Upon completion of this method will be positioned at the + /// final token in the JSON value. If an exception is thrown the reader is reset to + /// the state it was in when the method was called. + /// + /// + /// + /// This method makes a copy of the data the reader acted on, so there is no caller + /// requirement to maintain data integrity beyond the return of this method. + /// + /// + /// + /// is using unsupported options. + /// + /// + /// The current token does not start or represent a value. + /// + /// + /// A value could not be read from the reader. + /// + public static JsonElement ParseValue(ref Utf8JsonReader reader) + { + bool ret = JsonDocument.TryParseValue(ref reader, out JsonDocument? document, shouldThrow: true, useArrayPools: false); + + Debug.Assert(ret, "TryParseValue returned false with shouldThrow: true."); + Debug.Assert(document != null, "null document returned with shouldThrow: true."); + return document.RootElement; + } + + /// + /// Attempts to parse one JSON value (including objects or arrays) from the provided reader. + /// + /// The reader to read. + /// Receives the parsed element. + /// + /// if a value was read and parsed into a JsonElement, + /// if the reader ran out of data while parsing. + /// All other situations result in an exception being thrown. + /// + /// + /// + /// If the property of + /// is or , the + /// reader will be advanced by one call to to determine + /// the start of the value. + /// + /// + /// + /// Upon completion of this method will be positioned at the + /// final token in the JSON value. If an exception is thrown, or + /// is returned, the reader is reset to the state it was in when the method was called. + /// + /// + /// + /// This method makes a copy of the data the reader acted on, so there is no caller + /// requirement to maintain data integrity beyond the return of this method. + /// + /// + /// + /// is using unsupported options. + /// + /// + /// The current token does not start or represent a value. + /// + /// + /// A value could not be read from the reader. + /// + public static bool TryParseValue(ref Utf8JsonReader reader, [NotNullWhen(true)] out JsonElement? element) + { + bool ret = JsonDocument.TryParseValue(ref reader, out JsonDocument? document, shouldThrow: false, useArrayPools: false); + element = document?.RootElement; + return ret; + } + } +} diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.cs index 49215698e7c38a..c339aa766c92d1 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.cs @@ -27,17 +27,6 @@ internal JsonElement(JsonDocument parent, int idx) _idx = idx; } - // Currently used only as an optimization by the serializer, which does not want to - // return elements that are based on the pattern. - internal static JsonElement ParseValue(ref Utf8JsonReader reader) - { - bool ret = JsonDocument.TryParseValue(ref reader, out JsonDocument? document, shouldThrow: true, useArrayPools: false); - - Debug.Assert(ret != false, "Parse returned false with shouldThrow: true."); - Debug.Assert(document != null, "null document returned with shouldThrow: true."); - return document.RootElement; - } - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private JsonTokenType TokenType { diff --git a/src/libraries/System.Text.Json/tests/JsonElementReadTests.cs b/src/libraries/System.Text.Json/tests/JsonElementReadTests.cs new file mode 100644 index 00000000000000..0b22ada306443e --- /dev/null +++ b/src/libraries/System.Text.Json/tests/JsonElementReadTests.cs @@ -0,0 +1,145 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; +using System.Buffers; +using System.IO; +using System.Text.Encodings.Web; +using System.Collections.Generic; + +namespace System.Text.Json.Tests +{ + public static class JsonElementReadTests + { + public static IEnumerable ElementParseCases + { + get + { + yield return new object[] { "null", JsonValueKind.Null }; + yield return new object[] { "true", JsonValueKind.True }; + yield return new object[] { "false", JsonValueKind.False }; + yield return new object[] { "\"MyString\"", JsonValueKind.String }; + yield return new object[] { "{}", JsonValueKind.Object }; + yield return new object[] { "[]", JsonValueKind.Array }; + } + } + + [Theory] + [MemberData(nameof(ElementParseCases))] + public static void ParseValue(string json, JsonValueKind kind) + { + var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json)); + + JsonElement? element = JsonElement.ParseValue(ref reader); + Assert.Equal(kind, element!.Value.ValueKind); + } + + [Theory] + [MemberData(nameof(ElementParseCases))] + public static void TryParseValue(string json, JsonValueKind kind) + { + var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json)); + + bool success = JsonElement.TryParseValue(ref reader, out JsonElement? element); + Assert.True(success); + Assert.Equal(kind, element!.Value.ValueKind); + } + + public static IEnumerable ElementParsePartialDataCases + { + get + { + yield return new object[] { "\"MyString"}; + yield return new object[] { "{" }; + yield return new object[] { "[" }; + } + } + + [Theory] + [MemberData(nameof(ElementParsePartialDataCases))] + public static void ParseValuePartialDataFail(string json) + { + var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json)); + + try + { + JsonElement.ParseValue(ref reader); + Assert.True(false, "Expected exception."); + } + catch (JsonException) { } + } + + [Theory] + [MemberData(nameof(ElementParsePartialDataCases))] + public static void TryParseValuePartialDataFail(string json) + { + var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json)); + + try + { + JsonElement.TryParseValue(ref reader, out JsonElement? element); + Assert.True(false, "Expected exception."); + } + catch (JsonException) { } + } + + [Theory] + [MemberData(nameof(ElementParsePartialDataCases))] + public static void ParseValueOutOfData(string json) + { + var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json), isFinalBlock: false, new JsonReaderState()); + + try + { + JsonElement.ParseValue(ref reader); + Assert.True(false, "Expected exception."); + } + catch (JsonException) { } + } + + [Theory] + [MemberData(nameof(ElementParsePartialDataCases))] + public static void TryParseValueOutOfData(string json) + { + var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json), isFinalBlock: false, new JsonReaderState()); + Assert.False(JsonElement.TryParseValue(ref reader, out JsonElement? element)); + } + + public static IEnumerable ElementParseInvalidDataCases + { + get + { + yield return new object[] { "nul" }; + yield return new object[] { "{]" }; + } + } + + [Theory] + [MemberData(nameof(ElementParseInvalidDataCases))] + public static void ParseValueInvalidDataFail(string json) + { + var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json)); + + try + { + JsonElement.ParseValue(ref reader); + Assert.True(false, "Expected exception."); + } + catch (JsonException) { } + } + + [Theory] + [MemberData(nameof(ElementParseInvalidDataCases))] + public static void TryParseValueInvalidDataFail(string json) + { + var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json)); + + try + { + JsonElement.TryParseValue(ref reader, out JsonElement? element); + Assert.True(false, "Expected exception."); + } + catch (JsonException) { } + } + } +} diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj index 8acc26263edcaa..788491326df06a 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj @@ -21,6 +21,7 @@ + From 30e7a959f0f7cbf964d0cfbe898313288c4acfff Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Tue, 20 Oct 2020 14:36:21 -0500 Subject: [PATCH 2/2] Feedback --- .../src/System/Text/Json/Document/JsonElement.Parse.cs | 2 +- .../{JsonElementReadTests.cs => JsonElementParseTests.cs} | 5 +---- .../System.Text.Json/tests/System.Text.Json.Tests.csproj | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) rename src/libraries/System.Text.Json/tests/{JsonElementReadTests.cs => JsonElementParseTests.cs} (97%) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.Parse.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.Parse.cs index ccbabec49bbe6f..516159a5f6ad0c 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.Parse.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.Parse.cs @@ -13,7 +13,7 @@ public readonly partial struct JsonElement /// /// The reader to read. /// - /// A JsonDocument representing the value (and nested values) read from the reader. + /// A JsonElement representing the value (and nested values) read from the reader. /// /// /// diff --git a/src/libraries/System.Text.Json/tests/JsonElementReadTests.cs b/src/libraries/System.Text.Json/tests/JsonElementParseTests.cs similarity index 97% rename from src/libraries/System.Text.Json/tests/JsonElementReadTests.cs rename to src/libraries/System.Text.Json/tests/JsonElementParseTests.cs index 0b22ada306443e..c29851a603e6f1 100644 --- a/src/libraries/System.Text.Json/tests/JsonElementReadTests.cs +++ b/src/libraries/System.Text.Json/tests/JsonElementParseTests.cs @@ -2,14 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. using Xunit; -using System.Buffers; -using System.IO; -using System.Text.Encodings.Web; using System.Collections.Generic; namespace System.Text.Json.Tests { - public static class JsonElementReadTests + public static class JsonElementParseTests { public static IEnumerable ElementParseCases { diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj index 788491326df06a..215f56813045f8 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj @@ -21,7 +21,7 @@ - +