Skip to content

Commit

Permalink
Add JsonNode.ParseAsync public API (#90006)
Browse files Browse the repository at this point in the history
* Add `JsonNode.ParseAsync` public API

* Regenerate ref assemblies

* Change implementation to use unrented document
  • Loading branch information
DoctorKrolic authored Aug 7, 2023
1 parent 4415472 commit e98db16
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 13 deletions.
6 changes: 3 additions & 3 deletions src/libraries/System.Text.Json/ref/System.Text.Json.cs
Original file line number Diff line number Diff line change
Expand Up @@ -774,7 +774,7 @@ internal JsonNode() { }
public static System.Text.Json.Nodes.JsonNode? Parse(System.ReadOnlySpan<byte> utf8Json, System.Text.Json.Nodes.JsonNodeOptions? nodeOptions = default(System.Text.Json.Nodes.JsonNodeOptions?), System.Text.Json.JsonDocumentOptions documentOptions = default(System.Text.Json.JsonDocumentOptions)) { throw null; }
public static System.Text.Json.Nodes.JsonNode? Parse([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("Json")] string json, System.Text.Json.Nodes.JsonNodeOptions? nodeOptions = default(System.Text.Json.Nodes.JsonNodeOptions?), System.Text.Json.JsonDocumentOptions documentOptions = default(System.Text.Json.JsonDocumentOptions)) { throw null; }
public static System.Text.Json.Nodes.JsonNode? Parse(ref System.Text.Json.Utf8JsonReader reader, System.Text.Json.Nodes.JsonNodeOptions? nodeOptions = default(System.Text.Json.Nodes.JsonNodeOptions?)) { throw null; }

public static System.Threading.Tasks.Task<System.Text.Json.Nodes.JsonNode?> ParseAsync(System.IO.Stream utf8Json, System.Text.Json.Nodes.JsonNodeOptions? nodeOptions = default(System.Text.Json.Nodes.JsonNodeOptions?), System.Text.Json.JsonDocumentOptions documentOptions = default(System.Text.Json.JsonDocumentOptions), System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
[System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Creating JsonValue instances with non-primitive types requires generating code at runtime.")]
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("Creating JsonValue instances with non-primitive types is not compatible with trimming. It can result in non-primitive types being serialized, which may have their members trimmed.")]
public void ReplaceWith<T>(T value) { throw null; }
Expand Down Expand Up @@ -1217,12 +1217,12 @@ public static partial class JsonMetadataServices
public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<TCollection> CreateIReadOnlyDictionaryInfo<TCollection, TKey, TValue>(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.Metadata.JsonCollectionInfoValues<TCollection> collectionInfo) where TCollection : System.Collections.Generic.IReadOnlyDictionary<TKey, TValue> where TKey : notnull { throw null; }
public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<TCollection> CreateISetInfo<TCollection, TElement>(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.Metadata.JsonCollectionInfoValues<TCollection> collectionInfo) where TCollection : System.Collections.Generic.ISet<TElement> { throw null; }
public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<TCollection> CreateListInfo<TCollection, TElement>(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.Metadata.JsonCollectionInfoValues<TCollection> collectionInfo) where TCollection : System.Collections.Generic.List<TElement> { throw null; }
public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<Memory<TElement>> CreateMemoryInfo<TElement>(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.Metadata.JsonCollectionInfoValues<Memory<TElement>> collectionInfo) { throw null; }
public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<System.Memory<TElement>> CreateMemoryInfo<TElement>(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.Metadata.JsonCollectionInfoValues<System.Memory<TElement>> collectionInfo) { throw null; }
public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<T> CreateObjectInfo<T>(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.Metadata.JsonObjectInfoValues<T> objectInfo) where T : notnull { throw null; }
public static System.Text.Json.Serialization.Metadata.JsonPropertyInfo CreatePropertyInfo<T>(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.Metadata.JsonPropertyInfoValues<T> propertyInfo) { throw null; }
public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<TCollection> CreateQueueInfo<TCollection>(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.Metadata.JsonCollectionInfoValues<TCollection> collectionInfo, System.Action<TCollection, object?> addFunc) where TCollection : System.Collections.IEnumerable { throw null; }
public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<TCollection> CreateQueueInfo<TCollection, TElement>(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.Metadata.JsonCollectionInfoValues<TCollection> collectionInfo) where TCollection : System.Collections.Generic.Queue<TElement> { throw null; }
public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<ReadOnlyMemory<TElement>> CreateReadOnlyMemoryInfo<TElement>(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.Metadata.JsonCollectionInfoValues<ReadOnlyMemory<TElement>> collectionInfo) { throw null; }
public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<System.ReadOnlyMemory<TElement>> CreateReadOnlyMemoryInfo<TElement>(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.Metadata.JsonCollectionInfoValues<System.ReadOnlyMemory<TElement>> collectionInfo) { throw null; }
public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<TCollection> CreateStackInfo<TCollection>(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.Metadata.JsonCollectionInfoValues<TCollection> collectionInfo, System.Action<TCollection, object?> addFunc) where TCollection : System.Collections.IEnumerable { throw null; }
public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<TCollection> CreateStackInfo<TCollection, TElement>(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.Metadata.JsonCollectionInfoValues<TCollection> collectionInfo) where TCollection : System.Collections.Generic.Stack<TElement> { throw null; }
public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<T> CreateValueInfo<T>(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.JsonConverter converter) { throw null; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,24 @@ private static async Task<JsonDocument> ParseAsyncCore(
}
}

internal static async Task<JsonDocument> ParseAsyncCoreUnrented(
Stream utf8Json,
JsonDocumentOptions options = default,
CancellationToken cancellationToken = default)
{
ArraySegment<byte> drained = await ReadToEndAsync(utf8Json, cancellationToken).ConfigureAwait(false);
Debug.Assert(drained.Array != null);

byte[] owned = new byte[drained.Count];
Buffer.BlockCopy(drained.Array, 0, owned, 0, drained.Count);

// Holds document content, clear it before returning it.
drained.AsSpan().Clear();
ArrayPool<byte>.Shared.Return(drained.Array);

return ParseUnrented(owned.AsMemory(), options.GetReaderOptions());
}

/// <summary>
/// Parses text representing a single JSON value into a JsonDocument.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Text.Json.Serialization.Converters;
using System.Threading;
using System.Threading.Tasks;

namespace System.Text.Json.Nodes
{
Expand Down Expand Up @@ -126,5 +128,34 @@ public abstract partial class JsonNode
JsonElement element = JsonElement.ParseValue(utf8Json, documentOptions);
return JsonNodeConverter.Create(element, nodeOptions);
}

/// <summary>
/// Parse a <see cref="Stream"/> as UTF-8 encoded data representing a single JSON value into a
/// <see cref="JsonNode"/>. The Stream will be read to completion.
/// </summary>
/// <param name="utf8Json">JSON text to parse.</param>
/// <param name="nodeOptions">Options to control the node behavior after parsing.</param>
/// <param name="documentOptions">Options to control the document behavior during parsing.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>
/// A <see cref="Task"/> to produce a <see cref="JsonNode"/> representation of the JSON value.
/// </returns>
/// <exception cref="JsonException">
/// <paramref name="utf8Json"/> does not represent a valid single JSON value.
/// </exception>
public static async Task<JsonNode?> ParseAsync(
Stream utf8Json,
JsonNodeOptions? nodeOptions = null,
JsonDocumentOptions documentOptions = default,
CancellationToken cancellationToken = default)
{
if (utf8Json is null)
{
ThrowHelper.ThrowArgumentNullException(nameof(utf8Json));
}

JsonDocument document = await JsonDocument.ParseAsyncCoreUnrented(utf8Json, documentOptions, cancellationToken).ConfigureAwait(false);
return JsonNodeConverter.Create(document.RootElement, nodeOptions);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.IO;
using System.Reflection;
using System.Text.Json.Serialization.Tests;
using System.Threading.Tasks;
using Xunit;

namespace System.Text.Json.Nodes.Tests
Expand Down Expand Up @@ -125,27 +126,30 @@ public static void Parse_Fail()
}

[Fact]
public static void NullReference_Fail()
public static async Task NullReference_Fail()
{
Assert.Throws<ArgumentNullException>(() => JsonSerializer.Deserialize<JsonNode>((string)null));
Assert.Throws<ArgumentNullException>(() => JsonNode.Parse((string)null));
Assert.Throws<ArgumentNullException>(() => JsonNode.Parse((Stream)null));
await Assert.ThrowsAsync<ArgumentNullException>(() => JsonNode.ParseAsync(null));
}

[Fact]
public static void NullLiteral()
public static async Task NullLiteral()
{
Assert.Null(JsonSerializer.Deserialize<JsonNode>("null"));
Assert.Null(JsonNode.Parse("null"));

using (MemoryStream stream = new MemoryStream("null"u8.ToArray()))
{
Assert.Null(JsonNode.Parse(stream));
stream.Position = 0;
Assert.Null(await JsonNode.ParseAsync(stream));
}
}

[Fact]
public static void InternalValueFields()
public static async Task InternalValueFields()
{
// Use reflection to inspect the internal state of the 3 fields that hold values.
// There is not another way to verify, and using a debug watch causes nodes to be created.
Expand Down Expand Up @@ -196,19 +200,51 @@ void Test()
Assert.Equal(SimpleTestClass.s_json.StripWhitespace(), actual);
}
}

using (MemoryStream stream = new MemoryStream(SimpleTestClass.s_data))
{
// Only JsonElement is present.
JsonNode node = await JsonNode.ParseAsync(stream);
object jsonDictionary = jsonDictionaryField.GetValue(node);
Assert.Null(jsonDictionary); // Value is null until converted from JsonElement.
Assert.NotNull(elementField.GetValue(node));
Test();

// Cause the single JsonElement to expand into individual JsonElement nodes.
Assert.Equal(1, node.AsObject()["MyInt16"].GetValue<int>());
Assert.Null(elementField.GetValue(node));

jsonDictionary = jsonDictionaryField.GetValue(node);
Assert.NotNull(jsonDictionary);

Assert.NotNull(listField.GetValue(jsonDictionary));
Assert.NotNull(dictionaryField.GetValue(jsonDictionary)); // The dictionary threshold was reached.
Test();

void Test()
{
string actual = node.ToJsonString();

// Replace the escaped "+" sign used with DateTimeOffset.
actual = actual.Replace("\\u002B", "+");

Assert.Equal(SimpleTestClass.s_json.StripWhitespace(), actual);
}
}
}

[Fact]
public static void ReadSimpleObjectWithTrailingTrivia()
public static async Task ReadSimpleObjectWithTrailingTrivia()
{
byte[] data = Encoding.UTF8.GetBytes(SimpleTestClass.s_json + " /* Multi\r\nLine Comment */\t");
using (MemoryStream stream = new MemoryStream(data))

var options = new JsonDocumentOptions
{
var options = new JsonDocumentOptions
{
CommentHandling = JsonCommentHandling.Skip
};
CommentHandling = JsonCommentHandling.Skip
};

using (MemoryStream stream = new MemoryStream(data))
{
JsonNode node = JsonNode.Parse(stream, nodeOptions: null, options);

string actual = node.ToJsonString();
Expand All @@ -217,16 +253,33 @@ public static void ReadSimpleObjectWithTrailingTrivia()

Assert.Equal(SimpleTestClass.s_json.StripWhitespace(), actual);
}

using (MemoryStream stream = new MemoryStream(data))
{
JsonNode node = await JsonNode.ParseAsync(stream, nodeOptions: null, options);

string actual = node.ToJsonString();
// Replace the escaped "+" sign used with DateTimeOffset.
actual = actual.Replace("\\u002B", "+");

Assert.Equal(SimpleTestClass.s_json.StripWhitespace(), actual);
}
}

[Fact]
public static void ReadPrimitives()
public static async Task ReadPrimitives()
{
using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(@"1")))
{
int i = JsonNode.Parse(stream).AsValue().GetValue<int>();
Assert.Equal(1, i);
}

using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(@"1")))
{
int i = (await JsonNode.ParseAsync(stream)).AsValue().GetValue<int>();
Assert.Equal(1, i);
}
}

[Fact]
Expand Down

0 comments on commit e98db16

Please sign in to comment.