-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add public JsonElement.ParseValue() and TryParseValue() #43601
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
Comment on lines
+340
to
+341
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice! |
||
} | ||
|
||
internal static bool TryParseValue( | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -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 | ||||||||||||
{ | ||||||||||||
/// <summary> | ||||||||||||
/// Parses one JSON value (including objects or arrays) from the provided reader. | ||||||||||||
/// </summary> | ||||||||||||
/// <param name="reader">The reader to read.</param> | ||||||||||||
/// <returns> | ||||||||||||
/// A JsonElement representing the value (and nested values) read from the reader. | ||||||||||||
/// </returns> | ||||||||||||
/// <remarks> | ||||||||||||
/// <para> | ||||||||||||
/// If the <see cref="Utf8JsonReader.TokenType"/> property of <paramref name="reader"/> | ||||||||||||
/// is <see cref="JsonTokenType.PropertyName"/> or <see cref="JsonTokenType.None"/>, the | ||||||||||||
/// reader will be advanced by one call to <see cref="Utf8JsonReader.Read"/> to determine | ||||||||||||
/// the start of the value. | ||||||||||||
/// </para> | ||||||||||||
/// | ||||||||||||
/// <para> | ||||||||||||
/// Upon completion of this method <paramref name="reader"/> 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. | ||||||||||||
/// </para> | ||||||||||||
/// | ||||||||||||
/// <para> | ||||||||||||
/// 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. | ||||||||||||
/// </para> | ||||||||||||
/// </remarks> | ||||||||||||
/// <exception cref="ArgumentException"> | ||||||||||||
/// <paramref name="reader"/> is using unsupported options. | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should either update the published docs to match this phrasing, or update this new phrasing to match the published one on JsonDocument.ParseValue: FWIW, I prefer the phrasing in this PR over There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm it's always been "contains unsupported options" on the Document side. So the doc review changed to "is using unsupported options.", so it makes sense to change all copies of that sentence. cc @tdykstra |
||||||||||||
/// </exception> | ||||||||||||
/// <exception cref="ArgumentException"> | ||||||||||||
/// The current <paramref name="reader"/> token does not start or represent a value. | ||||||||||||
/// </exception> | ||||||||||||
/// <exception cref="JsonException"> | ||||||||||||
/// A value could not be read from the reader. | ||||||||||||
/// </exception> | ||||||||||||
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; | ||||||||||||
} | ||||||||||||
|
||||||||||||
/// <summary> | ||||||||||||
/// Attempts to parse one JSON value (including objects or arrays) from the provided reader. | ||||||||||||
/// </summary> | ||||||||||||
/// <param name="reader">The reader to read.</param> | ||||||||||||
/// <param name="element">Receives the parsed element.</param> | ||||||||||||
/// <returns> | ||||||||||||
/// <see langword="true"/> if a value was read and parsed into a JsonElement, | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Matching https://docs.microsoft.com/en-us/dotnet/api/system.text.json.jsondocument.tryparsevalue?view=netcore-3.1
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks. I'll also update the |
||||||||||||
/// <see langword="false"/> if the reader ran out of data while parsing. | ||||||||||||
/// All other situations result in an exception being thrown. | ||||||||||||
/// </returns> | ||||||||||||
/// <remarks> | ||||||||||||
/// <para> | ||||||||||||
/// If the <see cref="Utf8JsonReader.TokenType"/> property of <paramref name="reader"/> | ||||||||||||
/// is <see cref="JsonTokenType.PropertyName"/> or <see cref="JsonTokenType.None"/>, the | ||||||||||||
/// reader will be advanced by one call to <see cref="Utf8JsonReader.Read"/> to determine | ||||||||||||
/// the start of the value. | ||||||||||||
/// </para> | ||||||||||||
/// | ||||||||||||
/// <para> | ||||||||||||
/// Upon completion of this method <paramref name="reader"/> will be positioned at the | ||||||||||||
/// final token in the JSON value. If an exception is thrown, or <see langword="false"/> | ||||||||||||
/// is returned, the reader is reset to the state it was in when the method was called. | ||||||||||||
/// </para> | ||||||||||||
/// | ||||||||||||
/// <para> | ||||||||||||
/// 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. | ||||||||||||
/// </para> | ||||||||||||
/// </remarks> | ||||||||||||
/// <exception cref="ArgumentException"> | ||||||||||||
/// <paramref name="reader"/> is using unsupported options. | ||||||||||||
/// </exception> | ||||||||||||
/// <exception cref="ArgumentException"> | ||||||||||||
/// The current <paramref name="reader"/> token does not start or represent a value. | ||||||||||||
/// </exception> | ||||||||||||
/// <exception cref="JsonException"> | ||||||||||||
/// A value could not be read from the reader. | ||||||||||||
/// </exception> | ||||||||||||
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; | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe, only set
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The existing code is shorter. Since the if (JsonDocument.TryParseValue(...
{
element = document.RootElement;
return true;
}
element = null;
return false; which is equivalent in functionality to the current code. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missed that part. Makes sense. |
||||||||||||
return ret; | ||||||||||||
} | ||||||||||||
} | ||||||||||||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -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<object[]> 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 }; | ||||||
Comment on lines
+17
to
+18
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a number token and a string containing escaped characters. |
||||||
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); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also assert the state of the reader after the call to |
||||||
} | ||||||
|
||||||
[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); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here. Where is the reader now after the call which mutated it? |
||||||
} | ||||||
|
||||||
public static IEnumerable<object[]> ElementParsePartialDataCases | ||||||
{ | ||||||
get | ||||||
{ | ||||||
yield return new object[] { "\"MyString"}; | ||||||
yield return new object[] { "{" }; | ||||||
yield return new object[] { "[" }; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add empty string an string with just whitespace. |
||||||
} | ||||||
} | ||||||
|
||||||
[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."); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see another pattern in the Document tests that as an Exception variable and assigns that within the catch plus a couple Asserts for that variable, so I'll change to that which avoids the literal string. |
||||||
} | ||||||
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."); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add test that the reader state doesn't change if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just checking to see if BytesConsumed is zero that should be sufficient -- I'll add that. As mentioned in the PR description, this PR doesn't change the core logic, so it doesn't duplicate the bulk of the tests. |
||||||
} | ||||||
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)); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Verify that Additionally, maybe pass in an already initialized There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since a |
||||||
} | ||||||
|
||||||
public static IEnumerable<object[]> 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) { } | ||||||
} | ||||||
} | ||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JsonElement
is a struct. What doesNotNullWhen(true)
attribute mean for value types like this?I would have thought we wouldn't need it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TryParseValue's out param returns
Nullable<JsonElement>
so we need the attribute. UsingNullable
instead ofdefault(JsonElement)
prevents any possible misuse ofdefault(JsonElement)
and perhaps a bit less on the stack.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha. That makes more sense now :) Thanks.