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..516159a5f6ad0c --- /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 JsonElement 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/JsonElementParseTests.cs b/src/libraries/System.Text.Json/tests/JsonElementParseTests.cs new file mode 100644 index 00000000000000..c29851a603e6f1 --- /dev/null +++ b/src/libraries/System.Text.Json/tests/JsonElementParseTests.cs @@ -0,0 +1,142 @@ +// 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.Collections.Generic; + +namespace System.Text.Json.Tests +{ + public static class JsonElementParseTests + { + 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..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,6 +21,7 @@ +