Add interop between serializer and DOMs (#56112)
authorSteve Harter <steveharter@users.noreply.github.com>
Wed, 28 Jul 2021 01:35:54 +0000 (20:35 -0500)
committerGitHub <noreply@github.com>
Wed, 28 Jul 2021 01:35:54 +0000 (20:35 -0500)
26 files changed:
src/libraries/System.Text.Json/ref/System.Text.Json.cs
src/libraries/System.Text.Json/src/System.Text.Json.csproj
src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.Parse.cs
src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.cs
src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.cs
src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.To.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonNodeConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Document.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Element.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Node.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.String.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.ByteArray.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Document.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Element.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Node.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.String.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs
src/libraries/System.Text.Json/tests/Common/JsonSerializerWrapperForString.cs
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/JsonSerializerWrapper.cs
src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/DomTests.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonSerializerApiValidation.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonSerializerWrapperForString.cs
src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/MetadataTests.cs
src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/NumberHandlingTests.cs
src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.cs
src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj

index c376591..99a987c 100644 (file)
@@ -199,6 +199,15 @@ namespace System.Text.Json
         public static object? Deserialize(string json, System.Type returnType, System.Text.Json.JsonSerializerOptions? options = null) { throw null; }
         public static object? Deserialize(string json, System.Type returnType, System.Text.Json.Serialization.JsonSerializerContext context) { throw null; }
         [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")]
+        public static object? Deserialize(this System.Text.Json.JsonDocument document, System.Type returnType, System.Text.Json.JsonSerializerOptions? options = null) { throw null; }
+        public static object? Deserialize(this System.Text.Json.JsonDocument document, System.Type returnType, System.Text.Json.Serialization.JsonSerializerContext context) { throw null; }
+        [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")]
+        public static object? Deserialize(this System.Text.Json.JsonElement element, System.Type returnType, System.Text.Json.JsonSerializerOptions? options = null) { throw null; }
+        public static object? Deserialize(this System.Text.Json.JsonElement element, System.Type returnType, System.Text.Json.Serialization.JsonSerializerContext context) { throw null; }
+        [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")]
+        public static object? Deserialize(this System.Text.Json.Nodes.JsonNode? node, System.Type returnType, System.Text.Json.JsonSerializerOptions? options = null) { throw null; }
+        public static object? Deserialize(this System.Text.Json.Nodes.JsonNode? node, System.Type returnType, System.Text.Json.Serialization.JsonSerializerContext context) { throw null; }
+        [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")]
         public static object? Deserialize(ref System.Text.Json.Utf8JsonReader reader, System.Type returnType, System.Text.Json.JsonSerializerOptions? options = null) { throw null; }
         public static object? Deserialize(ref System.Text.Json.Utf8JsonReader reader, System.Type returnType, System.Text.Json.Serialization.JsonSerializerContext context) { throw null; }
         [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")]
@@ -222,6 +231,15 @@ namespace System.Text.Json
         public static TValue? Deserialize<TValue>(string json, System.Text.Json.JsonSerializerOptions? options = null) { throw null; }
         public static TValue? Deserialize<TValue>(string json, System.Text.Json.Serialization.Metadata.JsonTypeInfo<TValue> jsonTypeInfo) { throw null; }
         [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")]
+        public static TValue? Deserialize<TValue>(this System.Text.Json.JsonDocument document, System.Text.Json.JsonSerializerOptions? options = null) { throw null; }
+        public static TValue? Deserialize<TValue>(this System.Text.Json.JsonDocument document, System.Text.Json.Serialization.Metadata.JsonTypeInfo<TValue> jsonTypeInfo) { throw null; }
+        [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")]
+        public static TValue? Deserialize<TValue>(this System.Text.Json.JsonElement element, System.Text.Json.JsonSerializerOptions? options = null) { throw null; }
+        public static TValue? Deserialize<TValue>(this System.Text.Json.JsonElement element, System.Text.Json.Serialization.Metadata.JsonTypeInfo<TValue> jsonTypeInfo) { throw null; }
+        [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")]
+        public static TValue? Deserialize<TValue>(this System.Text.Json.Nodes.JsonNode? node, System.Text.Json.JsonSerializerOptions? options = null) { throw null; }
+        public static TValue? Deserialize<TValue>(this System.Text.Json.Nodes.JsonNode? node, System.Text.Json.Serialization.Metadata.JsonTypeInfo<TValue> jsonTypeInfo) { throw null; }
+        [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")]
         public static TValue? Deserialize< TValue>(ref System.Text.Json.Utf8JsonReader reader, System.Text.Json.JsonSerializerOptions? options = null) { throw null; }
         public static TValue? Deserialize<TValue>(ref System.Text.Json.Utf8JsonReader reader, System.Text.Json.Serialization.Metadata.JsonTypeInfo<TValue> jsonTypeInfo) { throw null; }
         [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")]
@@ -240,6 +258,24 @@ namespace System.Text.Json
         public static System.Threading.Tasks.Task SerializeAsync<TValue>(System.IO.Stream utf8Json, TValue value, System.Text.Json.JsonSerializerOptions? options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public static System.Threading.Tasks.Task SerializeAsync<TValue>(System.IO.Stream utf8Json, TValue value, System.Text.Json.Serialization.Metadata.JsonTypeInfo<TValue> jsonTypeInfo, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")]
+        public static System.Text.Json.JsonDocument SerializeToDocument(object? value, System.Type inputType, System.Text.Json.JsonSerializerOptions? options = null) { throw null; }
+        public static System.Text.Json.JsonDocument SerializeToDocument(object? value, System.Type inputType, System.Text.Json.Serialization.JsonSerializerContext context) { throw null; }
+        [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")]
+        public static System.Text.Json.JsonDocument SerializeToDocument<TValue>(TValue value, System.Text.Json.JsonSerializerOptions? options = null) { throw null; }
+        public static System.Text.Json.JsonDocument SerializeToDocument<TValue>(TValue value, System.Text.Json.Serialization.Metadata.JsonTypeInfo<TValue> jsonTypeInfo) { throw null; }
+        [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")]
+        public static System.Text.Json.JsonElement SerializeToElement(object? value, System.Type inputType, System.Text.Json.JsonSerializerOptions? options = null) { throw null; }
+        public static System.Text.Json.JsonElement SerializeToElement(object? value, System.Type inputType, System.Text.Json.Serialization.JsonSerializerContext context) { throw null; }
+        [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")]
+        public static System.Text.Json.JsonElement SerializeToElement<TValue>(TValue value, System.Text.Json.JsonSerializerOptions? options = null) { throw null; }
+        public static System.Text.Json.JsonElement SerializeToElement<TValue>(TValue value, System.Text.Json.Serialization.Metadata.JsonTypeInfo<TValue> jsonTypeInfo) { throw null; }
+        [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")]
+        public static System.Text.Json.Nodes.JsonNode? SerializeToNode(object? value, System.Type inputType, System.Text.Json.JsonSerializerOptions? options = null) { throw null; }
+        public static System.Text.Json.Nodes.JsonNode? SerializeToNode(object? value, System.Type inputType, System.Text.Json.Serialization.JsonSerializerContext context) { throw null; }
+        [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")]
+        public static System.Text.Json.Nodes.JsonNode? SerializeToNode<TValue>(TValue value, System.Text.Json.JsonSerializerOptions? options = null) { throw null; }
+        public static System.Text.Json.Nodes.JsonNode? SerializeToNode<TValue>(TValue value, System.Text.Json.Serialization.Metadata.JsonTypeInfo<TValue> jsonTypeInfo) { throw null; }
+        [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")]
         public static byte[] SerializeToUtf8Bytes(object? value, System.Type inputType, System.Text.Json.JsonSerializerOptions? options = null) { throw null; }
         public static byte[] SerializeToUtf8Bytes(object? value, System.Type inputType, System.Text.Json.Serialization.JsonSerializerContext context) { throw null; }
         [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")]
index 5a9ea18..031229d 100644 (file)
     <Compile Include="System\Text\Json\Serialization\IJsonOnDeserializing.cs" />
     <Compile Include="System\Text\Json\Serialization\IJsonOnSerialized.cs" />
     <Compile Include="System\Text\Json\Serialization\IJsonOnSerializing.cs" />
+    <Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.Document.cs" />
+    <Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.Element.cs" />
+    <Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.Node.cs" />
+    <Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.Document.cs" />
+    <Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.Element.cs" />
+    <Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.Node.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonSerializerContext.cs" />
     <Compile Include="System\Text\Json\Serialization\Metadata\FSharpCoreReflectionProxy.cs" />
     <Compile Include="System\Text\Json\Serialization\Metadata\JsonMetadataServices.Collections.cs" />
index 8df0c30..c287240 100644 (file)
@@ -46,7 +46,7 @@ namespace System.Text.Json
         /// </exception>
         public static JsonDocument Parse(ReadOnlyMemory<byte> utf8Json, JsonDocumentOptions options = default)
         {
-            return Parse(utf8Json, options.GetReaderOptions(), null);
+            return Parse(utf8Json, options.GetReaderOptions());
         }
 
         /// <summary>
@@ -80,7 +80,7 @@ namespace System.Text.Json
 
             if (utf8Json.IsSingleSegment)
             {
-                return Parse(utf8Json.First, readerOptions, null);
+                return Parse(utf8Json.First, readerOptions);
             }
 
             int length = checked((int)utf8Json.Length);
@@ -137,6 +137,15 @@ namespace System.Text.Json
             }
         }
 
+        internal static JsonDocument ParseRented(PooledByteBufferWriter utf8Json, JsonDocumentOptions options = default)
+        {
+            return Parse(
+                utf8Json.WrittenMemory,
+                options.GetReaderOptions(),
+                extraRentedArrayPoolBytes: null,
+                extraPooledByteBufferWriter: utf8Json);
+        }
+
         internal static JsonDocument ParseValue(Stream utf8Json, JsonDocumentOptions options)
         {
             Debug.Assert(utf8Json != null);
@@ -664,14 +673,15 @@ namespace System.Text.Json
             {
                 MetadataDb database = MetadataDb.CreateLocked(utf8Json.Length);
                 database.Append(tokenType, startLocation: 0, utf8Json.Length);
-                return new JsonDocument(utf8Json, database, extraRentedBytes: null);
+                return new JsonDocument(utf8Json, database);
             }
         }
 
         private static JsonDocument Parse(
             ReadOnlyMemory<byte> utf8Json,
             JsonReaderOptions readerOptions,
-            byte[]? extraRentedBytes)
+            byte[]? extraRentedArrayPoolBytes = null,
+            PooledByteBufferWriter? extraPooledByteBufferWriter = null)
         {
             ReadOnlySpan<byte> utf8JsonSpan = utf8Json.Span;
             var database = MetadataDb.CreateRented(utf8Json.Length, convertToAlloc: false);
@@ -691,7 +701,7 @@ namespace System.Text.Json
                 stack.Dispose();
             }
 
-            return new JsonDocument(utf8Json, database, extraRentedBytes);
+            return new JsonDocument(utf8Json, database, extraRentedArrayPoolBytes, extraPooledByteBufferWriter);
         }
 
         private static JsonDocument ParseUnrented(
@@ -729,7 +739,7 @@ namespace System.Text.Json
                 }
             }
 
-            return new JsonDocument(utf8Json, database, extraRentedBytes: null);
+            return new JsonDocument(utf8Json, database);
         }
 
         private static ArraySegment<byte> ReadToEnd(Stream stream)
index 26eabb0..2cb1321 100644 (file)
@@ -23,7 +23,13 @@ namespace System.Text.Json
     {
         private ReadOnlyMemory<byte> _utf8Json;
         private MetadataDb _parsedData;
-        private byte[]? _extraRentedBytes;
+
+        private byte[]? _extraRentedArrayPoolBytes;
+        private bool _hasExtraRentedArrayPoolBytes;
+
+        private PooledByteBufferWriter? _extraPooledByteBufferWriter;
+        private bool _hasExtraPooledByteBufferWriter;
+
         private (int, string?) _lastIndexAndString = (-1, null);
 
         internal bool IsDisposable { get; }
@@ -36,19 +42,33 @@ namespace System.Text.Json
         private JsonDocument(
             ReadOnlyMemory<byte> utf8Json,
             MetadataDb parsedData,
-            byte[]? extraRentedBytes,
+            byte[]? extraRentedArrayPoolBytes = null,
+            PooledByteBufferWriter? extraPooledByteBufferWriter = null,
             bool isDisposable = true)
         {
             Debug.Assert(!utf8Json.IsEmpty);
 
+            // We never have both rented fields.
+            Debug.Assert(extraRentedArrayPoolBytes == null || extraPooledByteBufferWriter == null);
+
             _utf8Json = utf8Json;
             _parsedData = parsedData;
-            _extraRentedBytes = extraRentedBytes;
+
+            if (_extraRentedArrayPoolBytes != null)
+            {
+                _hasExtraRentedArrayPoolBytes = true;
+                _extraRentedArrayPoolBytes = extraRentedArrayPoolBytes;
+            }
+            else if (extraPooledByteBufferWriter != null)
+            {
+                _hasExtraPooledByteBufferWriter = true;
+                _extraPooledByteBufferWriter = extraPooledByteBufferWriter;
+            }
 
             IsDisposable = isDisposable;
 
-            // extraRentedBytes better be null if we're not disposable.
-            Debug.Assert(isDisposable || extraRentedBytes == null);
+            // Both rented fields better be null if we're not disposable.
+            Debug.Assert(isDisposable || (_extraRentedArrayPoolBytes == null && _extraPooledByteBufferWriter == null));
         }
 
         /// <inheritdoc />
@@ -63,14 +83,22 @@ namespace System.Text.Json
             _parsedData.Dispose();
             _utf8Json = ReadOnlyMemory<byte>.Empty;
 
-            // When "extra rented bytes exist" they contain the document,
-            // and thus need to be cleared before being returned.
-            byte[]? extraRentedBytes = Interlocked.Exchange(ref _extraRentedBytes, null);
+            if (_hasExtraRentedArrayPoolBytes)
+            {
+                byte[]? extraRentedBytes = Interlocked.Exchange(ref _extraRentedArrayPoolBytes, null);
 
-            if (extraRentedBytes != null)
+                if (extraRentedBytes != null)
+                {
+                    // When "extra rented bytes exist" it contains the document,
+                    // and thus needs to be cleared before being returned.
+                    extraRentedBytes.AsSpan(0, length).Clear();
+                    ArrayPool<byte>.Shared.Return(extraRentedBytes);
+                }
+            }
+            else if (_hasExtraPooledByteBufferWriter)
             {
-                extraRentedBytes.AsSpan(0, length).Clear();
-                ArrayPool<byte>.Shared.Return(extraRentedBytes);
+                PooledByteBufferWriter? extraBufferWriter = Interlocked.Exchange(ref _extraPooledByteBufferWriter, null);
+                extraBufferWriter?.Dispose();
             }
         }
 
@@ -184,7 +212,12 @@ namespace System.Text.Json
             return endIndex;
         }
 
-        private ReadOnlyMemory<byte> GetRawValue(int index, bool includeQuotes)
+        internal ReadOnlyMemory<byte> GetRootRawValue()
+        {
+            return GetRawValue(0, includeQuotes : true);
+        }
+
+        internal ReadOnlyMemory<byte> GetRawValue(int index, bool includeQuotes)
         {
             CheckNotDisposed();
 
@@ -764,7 +797,12 @@ namespace System.Text.Json
             ReadOnlyMemory<byte> segmentCopy = GetRawValue(index, includeQuotes: true).ToArray();
 
             JsonDocument newDocument =
-                new JsonDocument(segmentCopy, newDb, extraRentedBytes: null, isDisposable: false);
+                new JsonDocument(
+                    segmentCopy,
+                    newDb,
+                    extraRentedArrayPoolBytes: null,
+                    extraPooledByteBufferWriter: null,
+                    isDisposable: false);
 
             return newDocument.RootElement;
         }
index 18e69d8..5f30dd6 100644 (file)
@@ -1169,6 +1169,13 @@ namespace System.Text.Json
             return _parent.GetRawValueAsString(_idx);
         }
 
+        internal ReadOnlyMemory<byte> GetRawValue()
+        {
+            CheckValidInstance();
+
+            return _parent.GetRawValue(_idx, includeQuotes: true);
+        }
+
         internal string GetPropertyRawText()
         {
             CheckValidInstance();
index cbc3934..2abaaf0 100644 (file)
@@ -12,15 +12,13 @@ namespace System.Text.Json.Nodes
         /// <returns>JSON representation of current instance.</returns>
         public string ToJsonString(JsonSerializerOptions? options = null)
         {
-            using (var output = new PooledByteBufferWriter(JsonSerializerOptions.BufferSizeDefault))
+            using var output = new PooledByteBufferWriter(JsonSerializerOptions.BufferSizeDefault);
+            using (var writer = new Utf8JsonWriter(output, options == null ? default(JsonWriterOptions) : options.GetWriterOptions()))
             {
-                using (var writer = new Utf8JsonWriter(output, options == null ? default(JsonWriterOptions) : options.GetWriterOptions()))
-                {
-                    WriteTo(writer, options);
-                }
-
-                return JsonHelpers.Utf8GetString(output.WrittenMemory.ToArray());
+                WriteTo(writer, options);
             }
+
+            return JsonHelpers.Utf8GetString(output.WrittenMemory.ToArray());
         }
 
         /// <summary>
@@ -44,15 +42,13 @@ namespace System.Text.Json.Nodes
                 }
             }
 
-            using (var output = new PooledByteBufferWriter(JsonSerializerOptions.BufferSizeDefault))
+            using var output = new PooledByteBufferWriter(JsonSerializerOptions.BufferSizeDefault);
+            using (var writer = new Utf8JsonWriter(output, new JsonWriterOptions { Indented = true }))
             {
-                using (var writer = new Utf8JsonWriter(output, new JsonWriterOptions { Indented = true }))
-                {
-                    WriteTo(writer);
-                }
-
-                return JsonHelpers.Utf8GetString(output.WrittenMemory.ToArray());
+                WriteTo(writer);
             }
+
+            return JsonHelpers.Utf8GetString(output.WrittenMemory.ToArray());
         }
 
         /// <summary>
index 07da2c9..8d0f7bb 100644 (file)
@@ -28,6 +28,7 @@ namespace System.Text.Json.Serialization.Converters
             if (value == null)
             {
                 writer.WriteNullValue();
+                // Note JsonSerializer.Deserialize<T>(JsonNode?) also calls WriteNullValue() for a null + root JsonNode.
             }
             else
             {
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Document.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Document.cs
new file mode 100644 (file)
index 0000000..45f78a9
--- /dev/null
@@ -0,0 +1,173 @@
+// 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.CodeAnalysis;
+using System.Text.Json.Serialization;
+using System.Text.Json.Serialization.Metadata;
+
+namespace System.Text.Json
+{
+    public static partial class JsonSerializer
+    {
+        /// <summary>
+        /// Converts the <see cref="JsonDocument"/> representing a single JSON value into a <typeparamref name="TValue"/>.
+        /// </summary>
+        /// <returns>A <typeparamref name="TValue"/> representation of the JSON value.</returns>
+        /// <param name="document">The <see cref="JsonDocument"/> to convert.</param>
+        /// <param name="options">Options to control the behavior during parsing.</param>
+        /// <exception cref="System.ArgumentNullException">
+        /// <paramref name="document"/> is <see langword="null"/>.
+        /// </exception>
+        /// <exception cref="JsonException">
+        /// <typeparamref name="TValue" /> is not compatible with the JSON.
+        /// </exception>
+        /// <exception cref="NotSupportedException">
+        /// There is no compatible <see cref="System.Text.Json.Serialization.JsonConverter"/>
+        /// for <typeparamref name="TValue"/> or its serializable members.
+        /// </exception>
+        [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)]
+        public static TValue? Deserialize<TValue>(this JsonDocument document, JsonSerializerOptions? options = null)
+        {
+            if (document == null)
+            {
+                throw new ArgumentNullException(nameof(document));
+            }
+
+            return ReadUsingOptions<TValue>(document, typeof(TValue), options);
+        }
+
+        /// <summary>
+        /// Converts the <see cref="JsonDocument"/> representing a single JSON value into a <paramref name="returnType"/>.
+        /// </summary>
+        /// <returns>A <paramref name="returnType"/> representation of the JSON value.</returns>
+        /// <param name="document">The <see cref="JsonDocument"/> to convert.</param>
+        /// <param name="returnType">The type of the object to convert to and return.</param>
+        /// <param name="options">Options to control the behavior during parsing.</param>
+        /// <exception cref="System.ArgumentNullException">
+        /// <paramref name="document"/> or <paramref name="returnType"/> is <see langword="null"/>.
+        /// </exception>
+        /// <exception cref="JsonException">
+        /// <paramref name="returnType"/> is not compatible with the JSON.
+        /// </exception>
+        /// <exception cref="NotSupportedException">
+        /// There is no compatible <see cref="System.Text.Json.Serialization.JsonConverter"/>
+        /// for <paramref name="returnType"/> or its serializable members.
+        /// </exception>
+        [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)]
+        public static object? Deserialize(this JsonDocument document, Type returnType, JsonSerializerOptions? options = null)
+        {
+            if (document == null)
+            {
+                throw new ArgumentNullException(nameof(document));
+            }
+
+            if (returnType == null)
+            {
+                throw new ArgumentNullException(nameof(returnType));
+            }
+
+            return ReadUsingOptions<object?>(document, returnType, options);
+        }
+
+        /// <summary>
+        /// Converts the <see cref="JsonDocument"/> representing a single JSON value into a <typeparamref name="TValue"/>.
+        /// </summary>
+        /// <returns>A <typeparamref name="TValue"/> representation of the JSON value.</returns>
+        /// <param name="document">The <see cref="JsonDocument"/> to convert.</param>
+        /// <param name="jsonTypeInfo">Metadata about the type to convert.</param>
+        /// <exception cref="System.ArgumentNullException">
+        /// <paramref name="document"/> is <see langword="null"/>.
+        ///
+        /// -or-
+        ///
+        /// <paramref name="jsonTypeInfo"/> is <see langword="null"/>.
+        /// </exception>
+        /// <exception cref="JsonException">
+        /// <typeparamref name="TValue" /> is not compatible with the JSON.
+        /// </exception>
+        /// <exception cref="NotSupportedException">
+        /// There is no compatible <see cref="System.Text.Json.Serialization.JsonConverter"/>
+        /// for <typeparamref name="TValue"/> or its serializable members.
+        /// </exception>
+        public static TValue? Deserialize<TValue>(this JsonDocument document, JsonTypeInfo<TValue> jsonTypeInfo)
+        {
+            if (document == null)
+            {
+                throw new ArgumentNullException(nameof(document));
+            }
+
+            if (jsonTypeInfo == null)
+            {
+                throw new ArgumentNullException(nameof(jsonTypeInfo));
+            }
+
+            return ReadUsingMetadata<TValue>(document, jsonTypeInfo);
+        }
+
+        /// <summary>
+        /// Converts the <see cref="JsonDocument"/> representing a single JSON value into a <paramref name="returnType"/>.
+        /// </summary>
+        /// <returns>A <paramref name="returnType"/> representation of the JSON value.</returns>
+        /// <param name="document">The <see cref="JsonDocument"/> to convert.</param>
+        /// <param name="returnType">The type of the object to convert to and return.</param>
+        /// <param name="context">A metadata provider for serializable types.</param>
+        /// <exception cref="System.ArgumentNullException">
+        /// <paramref name="document"/> is <see langword="null"/>.
+        ///
+        /// -or-
+        ///
+        /// <paramref name="returnType"/> is <see langword="null"/>.
+        ///
+        /// -or-
+        ///
+        /// <paramref name="context"/> is <see langword="null"/>.
+        /// </exception>
+        /// <exception cref="JsonException">
+        /// The JSON is invalid.
+        ///
+        /// -or-
+        ///
+        /// <paramref name="returnType" /> is not compatible with the JSON.
+        ///
+        /// -or-
+        ///
+        /// There is remaining data in the string beyond a single JSON value.</exception>
+        /// <exception cref="NotSupportedException">
+        /// There is no compatible <see cref="System.Text.Json.Serialization.JsonConverter"/>
+        /// for <paramref name="returnType"/> or its serializable members.
+        /// </exception>
+        /// <exception cref="InvalidOperationException">
+        /// The <see cref="JsonSerializerContext.GetTypeInfo(Type)"/> method of the provided
+        /// <paramref name="context"/> returns <see langword="null"/> for the type to convert.
+        /// </exception>
+        public static object? Deserialize(this JsonDocument document, Type returnType, JsonSerializerContext context)
+        {
+            if (document == null)
+            {
+                throw new ArgumentNullException(nameof(document));
+            }
+
+            if (returnType == null)
+            {
+                throw new ArgumentNullException(nameof(returnType));
+            }
+
+            if (context == null)
+            {
+                throw new ArgumentNullException(nameof(context));
+            }
+
+            return ReadUsingMetadata<object?>(document, GetTypeInfo(context, returnType));
+        }
+
+        [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)]
+        private static TValue? ReadUsingOptions<TValue>(JsonDocument document, Type returnType, JsonSerializerOptions? options) =>
+            ReadUsingMetadata<TValue>(document, GetTypeInfo(returnType, options));
+
+        private static TValue? ReadUsingMetadata<TValue>(JsonDocument document, JsonTypeInfo jsonTypeInfo)
+        {
+            ReadOnlySpan<byte> utf8Json = document.GetRootRawValue().Span;
+            return ReadUsingMetadata<TValue>(utf8Json, jsonTypeInfo);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Element.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Element.cs
new file mode 100644 (file)
index 0000000..9419f64
--- /dev/null
@@ -0,0 +1,140 @@
+// 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.CodeAnalysis;
+using System.Text.Json.Serialization;
+using System.Text.Json.Serialization.Metadata;
+
+namespace System.Text.Json
+{
+    public static partial class JsonSerializer
+    {
+        /// <summary>
+        /// Converts the <see cref="JsonElement"/> representing a single JSON value into a <typeparamref name="TValue"/>.
+        /// </summary>
+        /// <returns>A <typeparamref name="TValue"/> representation of the JSON value.</returns>
+        /// <param name="element">The <see cref="JsonElement"/> to convert.</param>
+        /// <param name="options">Options to control the behavior during parsing.</param>
+        /// <exception cref="JsonException">
+        /// <typeparamref name="TValue" /> is not compatible with the JSON.
+        /// </exception>
+        /// <exception cref="NotSupportedException">
+        /// There is no compatible <see cref="System.Text.Json.Serialization.JsonConverter"/>
+        /// for <typeparamref name="TValue"/> or its serializable members.
+        /// </exception>
+        [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)]
+        public static TValue? Deserialize<TValue>(this JsonElement element, JsonSerializerOptions? options = null) =>
+            ReadUsingOptions<TValue>(element, typeof(TValue), options);
+
+        /// <summary>
+        /// Converts the <see cref="JsonElement"/> representing a single JSON value into a <paramref name="returnType"/>.
+        /// </summary>
+        /// <returns>A <paramref name="returnType"/> representation of the JSON value.</returns>
+        /// <param name="element">The <see cref="JsonElement"/> to convert.</param>
+        /// <param name="returnType">The type of the object to convert to and return.</param>
+        /// <param name="options">Options to control the behavior during parsing.</param>
+        /// <exception cref="System.ArgumentNullException">
+        /// <paramref name="returnType"/> is <see langword="null"/>.
+        /// </exception>
+        /// <exception cref="JsonException">
+        /// <paramref name="returnType"/> is not compatible with the JSON.
+        /// </exception>
+        /// <exception cref="NotSupportedException">
+        /// There is no compatible <see cref="System.Text.Json.Serialization.JsonConverter"/>
+        /// for <paramref name="returnType"/> or its serializable members.
+        /// </exception>
+        [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)]
+        public static object? Deserialize(this JsonElement element, Type returnType, JsonSerializerOptions? options = null)
+        {
+            if (returnType == null)
+            {
+                throw new ArgumentNullException(nameof(returnType));
+            }
+
+            return ReadUsingOptions<object?>(element, returnType, options);
+        }
+
+        /// <summary>
+        /// Converts the <see cref="JsonElement"/> representing a single JSON value into a <typeparamref name="TValue"/>.
+        /// </summary>
+        /// <returns>A <typeparamref name="TValue"/> representation of the JSON value.</returns>
+        /// <param name="element">The <see cref="JsonElement"/> to convert.</param>
+        /// <param name="jsonTypeInfo">Metadata about the type to convert.</param>
+        /// <exception cref="System.ArgumentNullException">
+        /// <paramref name="jsonTypeInfo"/> is <see langword="null"/>.
+        /// </exception>
+        /// <exception cref="JsonException">
+        /// <typeparamref name="TValue" /> is not compatible with the JSON.
+        /// </exception>
+        /// <exception cref="NotSupportedException">
+        /// There is no compatible <see cref="System.Text.Json.Serialization.JsonConverter"/>
+        /// for <typeparamref name="TValue"/> or its serializable members.
+        /// </exception>
+        public static TValue? Deserialize<TValue>(this JsonElement element, JsonTypeInfo<TValue> jsonTypeInfo)
+        {
+            if (jsonTypeInfo == null)
+            {
+                throw new ArgumentNullException(nameof(jsonTypeInfo));
+            }
+
+            return ReadUsingMetadata<TValue>(element, jsonTypeInfo);
+        }
+
+        /// <summary>
+        /// Converts the <see cref="JsonElement"/> representing a single JSON value into a <paramref name="returnType"/>.
+        /// </summary>
+        /// <returns>A <paramref name="returnType"/> representation of the JSON value.</returns>
+        /// <param name="element">The <see cref="JsonElement"/> to convert.</param>
+        /// <param name="returnType">The type of the object to convert to and return.</param>
+        /// <param name="context">A metadata provider for serializable types.</param>
+        /// <exception cref="System.ArgumentNullException">
+        /// <paramref name="returnType"/> is <see langword="null"/>.
+        ///
+        /// -or-
+        ///
+        /// <paramref name="context"/> is <see langword="null"/>.
+        /// </exception>
+        /// <exception cref="JsonException">
+        /// The JSON is invalid.
+        ///
+        /// -or-
+        ///
+        /// <paramref name="returnType" /> is not compatible with the JSON.
+        ///
+        /// -or-
+        ///
+        /// There is remaining data in the string beyond a single JSON value.</exception>
+        /// <exception cref="NotSupportedException">
+        /// There is no compatible <see cref="System.Text.Json.Serialization.JsonConverter"/>
+        /// for <paramref name="returnType"/> or its serializable members.
+        /// </exception>
+        /// <exception cref="InvalidOperationException">
+        /// The <see cref="JsonSerializerContext.GetTypeInfo(Type)"/> method of the provided
+        /// <paramref name="context"/> returns <see langword="null"/> for the type to convert.
+        /// </exception>
+        public static object? Deserialize(this JsonElement element, Type returnType, JsonSerializerContext context)
+        {
+            if (returnType == null)
+            {
+                throw new ArgumentNullException(nameof(returnType));
+            }
+
+            if (context == null)
+            {
+                throw new ArgumentNullException(nameof(context));
+            }
+
+            return ReadUsingMetadata<object?>(element, GetTypeInfo(context, returnType));
+        }
+
+        [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)]
+        private static TValue? ReadUsingOptions<TValue>(JsonElement element, Type returnType, JsonSerializerOptions? options) =>
+            ReadUsingMetadata<TValue>(element, GetTypeInfo(returnType, options));
+
+        private static TValue? ReadUsingMetadata<TValue>(JsonElement element, JsonTypeInfo jsonTypeInfo)
+        {
+            ReadOnlySpan<byte> utf8Json = element.GetRawValue().Span;
+            return ReadUsingMetadata<TValue>(utf8Json, jsonTypeInfo);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Node.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Node.cs
new file mode 100644 (file)
index 0000000..32de1de
--- /dev/null
@@ -0,0 +1,157 @@
+// 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;
+using System.Text.Json.Nodes;
+using System.Text.Json.Serialization;
+using System.Text.Json.Serialization.Metadata;
+
+namespace System.Text.Json
+{
+    public static partial class JsonSerializer
+    {
+        /// <summary>
+        /// Converts the <see cref="JsonNode"/> representing a single JSON value into a <typeparamref name="TValue"/>.
+        /// </summary>
+        /// <returns>A <typeparamref name="TValue"/> representation of the JSON value.</returns>
+        /// <param name="node">The <see cref="JsonNode"/> to convert.</param>
+        /// <param name="options">Options to control the behavior during parsing.</param>
+        /// <exception cref="JsonException">
+        /// <typeparamref name="TValue" /> is not compatible with the JSON.
+        /// </exception>
+        /// <exception cref="NotSupportedException">
+        /// There is no compatible <see cref="System.Text.Json.Serialization.JsonConverter"/>
+        /// for <typeparamref name="TValue"/> or its serializable members.
+        /// </exception>
+        [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)]
+        public static TValue? Deserialize<TValue>(this JsonNode? node, JsonSerializerOptions? options = null)
+        {
+            return ReadUsingOptions<TValue>(node, typeof(TValue), options);
+        }
+
+        /// <summary>
+        /// Converts the <see cref="JsonNode"/> representing a single JSON value into a <paramref name="returnType"/>.
+        /// </summary>
+        /// <returns>A <paramref name="returnType"/> representation of the JSON value.</returns>
+        /// <param name="node">The <see cref="JsonNode"/> to convert.</param>
+        /// <param name="returnType">The type of the object to convert to and return.</param>
+        /// <param name="options">Options to control the behavior during parsing.</param>
+        /// <exception cref="JsonException">
+        /// <paramref name="returnType"/> is not compatible with the JSON.
+        /// </exception>
+        /// <exception cref="NotSupportedException">
+        /// There is no compatible <see cref="System.Text.Json.Serialization.JsonConverter"/>
+        /// for <paramref name="returnType"/> or its serializable members.
+        /// </exception>
+        [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)]
+        public static object? Deserialize(this JsonNode? node, Type returnType, JsonSerializerOptions? options = null)
+        {
+            if (returnType == null)
+            {
+                throw new ArgumentNullException(nameof(returnType));
+            }
+
+            return ReadUsingOptions<object?>(node, returnType, options);
+        }
+
+        /// <summary>
+        /// Converts the <see cref="JsonNode"/> representing a single JSON value into a <typeparamref name="TValue"/>.
+        /// </summary>
+        /// <returns>A <typeparamref name="TValue"/> representation of the JSON value.</returns>
+        /// <param name="node">The <see cref="JsonNode"/> to convert.</param>
+        /// <param name="jsonTypeInfo">Metadata about the type to convert.</param>
+        /// <exception cref="System.ArgumentNullException">
+        /// <paramref name="jsonTypeInfo"/> is <see langword="null"/>.
+        /// </exception>
+        /// <exception cref="JsonException">
+        /// <typeparamref name="TValue" /> is not compatible with the JSON.
+        /// </exception>
+        /// <exception cref="NotSupportedException">
+        /// There is no compatible <see cref="System.Text.Json.Serialization.JsonConverter"/>
+        /// for <typeparamref name="TValue"/> or its serializable members.
+        /// </exception>
+        public static TValue? Deserialize<TValue>(this JsonNode? node, JsonTypeInfo<TValue> jsonTypeInfo)
+        {
+            if (jsonTypeInfo == null)
+            {
+                throw new ArgumentNullException(nameof(jsonTypeInfo));
+            }
+
+            return ReadUsingMetadata<TValue>(node, jsonTypeInfo);
+        }
+
+        /// <summary>
+        /// Converts the <see cref="JsonNode"/> representing a single JSON value into a <paramref name="returnType"/>.
+        /// </summary>
+        /// <returns>A <paramref name="returnType"/> representation of the JSON value.</returns>
+        /// <param name="node">The <see cref="JsonNode"/> to convert.</param>
+        /// <param name="returnType">The type of the object to convert to and return.</param>
+        /// <param name="context">A metadata provider for serializable types.</param>
+        /// <exception cref="System.ArgumentNullException">
+        /// <paramref name="returnType"/> is <see langword="null"/>.
+        ///
+        /// -or-
+        ///
+        /// <paramref name="context"/> is <see langword="null"/>.
+        /// </exception>
+        /// <exception cref="JsonException">
+        /// The JSON is invalid.
+        ///
+        /// -or-
+        ///
+        /// <paramref name="returnType" /> is not compatible with the JSON.
+        ///
+        /// -or-
+        ///
+        /// There is remaining data in the string beyond a single JSON value.</exception>
+        /// <exception cref="NotSupportedException">
+        /// There is no compatible <see cref="System.Text.Json.Serialization.JsonConverter"/>
+        /// for <paramref name="returnType"/> or its serializable members.
+        /// </exception>
+        /// <exception cref="InvalidOperationException">
+        /// The <see cref="JsonSerializerContext.GetTypeInfo(Type)"/> method of the provided
+        /// <paramref name="context"/> returns <see langword="null"/> for the type to convert.
+        /// </exception>
+        public static object? Deserialize(this JsonNode? node, Type returnType, JsonSerializerContext context)
+        {
+            if (returnType == null)
+            {
+                throw new ArgumentNullException(nameof(returnType));
+            }
+
+            if (context == null)
+            {
+                throw new ArgumentNullException(nameof(context));
+            }
+
+            return ReadUsingMetadata<object?>(node, GetTypeInfo(context, returnType));
+        }
+
+        [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)]
+        private static TValue? ReadUsingOptions<TValue>(JsonNode? node, Type returnType, JsonSerializerOptions? options) =>
+            ReadUsingMetadata<TValue>(node, GetTypeInfo(returnType, options));
+
+        private static TValue? ReadUsingMetadata<TValue>(JsonNode? node, JsonTypeInfo jsonTypeInfo)
+        {
+            JsonSerializerOptions options = jsonTypeInfo.Options;
+            Debug.Assert(options != null);
+
+            // For performance, share the same buffer across serialization and deserialization.
+            using var output = new PooledByteBufferWriter(options.DefaultBufferSize);
+            using (var writer = new Utf8JsonWriter(output, options.GetWriterOptions()))
+            {
+                if (node is null)
+                {
+                    writer.WriteNullValue();
+                }
+                else
+                {
+                    node.WriteTo(writer, options);
+                }
+            }
+
+            return ReadUsingMetadata<TValue>(output.WrittenMemory.Span, jsonTypeInfo);
+        }
+    }
+}
index 29e06ef..50add92 100644 (file)
@@ -262,7 +262,7 @@ namespace System.Text.Json
         /// <param name="returnType">The type of the object to convert to and return.</param>
         /// <param name="context">A metadata provider for serializable types.</param>
         /// <exception cref="System.ArgumentNullException">
-        /// <paramref name="json"/> is <see langword="null"/>.
+        /// <paramref name="json"/> or <paramref name="returnType"/> is <see langword="null"/>.
         ///
         /// -or-
         ///
@@ -307,7 +307,7 @@ namespace System.Text.Json
         /// <param name="returnType">The type of the object to convert to and return.</param>
         /// <param name="context">A metadata provider for serializable types.</param>
         /// <exception cref="System.ArgumentNullException">
-        /// <paramref name="json"/> is <see langword="null"/>.
+        /// <paramref name="json"/> or <paramref name="returnType"/> is <see langword="null"/>.
         ///
         /// -or-
         ///
index b64bcea..880ce55 100644 (file)
@@ -122,15 +122,13 @@ namespace System.Text.Json
         {
             JsonSerializerOptions options = jsonTypeInfo.Options;
 
-            using (var output = new PooledByteBufferWriter(options.DefaultBufferSize))
+            using var output = new PooledByteBufferWriter(options.DefaultBufferSize);
+            using (var writer = new Utf8JsonWriter(output, options.GetWriterOptions()))
             {
-                using (var writer = new Utf8JsonWriter(output, options.GetWriterOptions()))
-                {
-                    WriteUsingMetadata(writer, value, jsonTypeInfo);
-                }
-
-                return output.WrittenMemory.ToArray();
+                WriteUsingMetadata(writer, value, jsonTypeInfo);
             }
+
+            return output.WrittenMemory.ToArray();
         }
     }
 }
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Document.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Document.cs
new file mode 100644 (file)
index 0000000..1c5d7c7
--- /dev/null
@@ -0,0 +1,126 @@
+// 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;
+using System.Text.Json.Serialization;
+using System.Text.Json.Serialization.Metadata;
+
+namespace System.Text.Json
+{
+    public static partial class JsonSerializer
+    {
+        /// <summary>
+        /// Convert the provided value into a <see cref="JsonDocument"/>.
+        /// </summary>
+        /// <returns>A <see cref="JsonDocument"/> representation of the JSON value.</returns>
+        /// <param name="value">The value to convert.</param>
+        /// <param name="options">Options to control the conversion behavior.</param>
+        /// <exception cref="NotSupportedException">
+        /// There is no compatible <see cref="System.Text.Json.Serialization.JsonConverter"/>
+        /// for <typeparamref name="TValue"/> or its serializable members.
+        /// </exception>
+        [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)]
+        public static JsonDocument SerializeToDocument<TValue>(TValue value, JsonSerializerOptions? options = null) =>
+            WriteDocument(value, GetRuntimeType(value), options);
+
+        /// <summary>
+        /// Convert the provided value into a <see cref="JsonDocument"/>.
+        /// </summary>
+        /// <returns>A <see cref="JsonDocument"/> representation of the value.</returns>
+        /// <param name="value">The value to convert.</param>
+        /// <param name="inputType">The type of the <paramref name="value"/> to convert.</param>
+        /// <param name="options">Options to control the conversion behavior.</param>
+        /// <exception cref="ArgumentException">
+        /// <paramref name="inputType"/> is not compatible with <paramref name="value"/>.
+        /// </exception>
+        /// <exception cref="NotSupportedException">
+        /// <exception cref="ArgumentNullException">
+        /// <paramref name="inputType"/> is <see langword="null"/>.
+        /// </exception>
+        /// There is no compatible <see cref="System.Text.Json.Serialization.JsonConverter"/>
+        /// for <paramref name="inputType"/>  or its serializable members.
+        /// </exception>
+        [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)]
+        public static JsonDocument SerializeToDocument(object? value, Type inputType, JsonSerializerOptions? options = null) =>
+            WriteDocument(
+                value,
+                GetRuntimeTypeAndValidateInputType(value, inputType),
+                options);
+
+        /// <summary>
+        /// Convert the provided value into a <see cref="JsonDocument"/>.
+        /// </summary>
+        /// <returns>A <see cref="JsonDocument"/> representation of the value.</returns>
+        /// <param name="value">The value to convert.</param>
+        /// <param name="jsonTypeInfo">Metadata about the type to convert.</param>
+        /// <exception cref="NotSupportedException">
+        /// There is no compatible <see cref="Serialization.JsonConverter"/>
+        /// for <typeparamref name="TValue"/> or its serializable members.
+        /// </exception>
+        /// <exception cref="ArgumentNullException">
+        /// <paramref name="jsonTypeInfo"/> is <see langword="null"/>.
+        /// </exception>
+        public static JsonDocument SerializeToDocument<TValue>(TValue value, JsonTypeInfo<TValue> jsonTypeInfo)
+        {
+            if (jsonTypeInfo == null)
+            {
+                throw new ArgumentNullException(nameof(jsonTypeInfo));
+            }
+
+            return WriteDocument(value, jsonTypeInfo);
+        }
+
+        /// <summary>
+        /// Convert the provided value into a <see cref="JsonDocument"/>.
+        /// </summary>
+        /// <returns>A <see cref="JsonDocument"/> representation of the value.</returns>
+        /// <param name="value">The value to convert.</param>
+        /// <param name="inputType">The type of the <paramref name="value"/> to convert.</param>
+        /// <param name="context">A metadata provider for serializable types.</param>
+        /// <exception cref="NotSupportedException">
+        /// There is no compatible <see cref="System.Text.Json.Serialization.JsonConverter"/>
+        /// for <paramref name="inputType"/> or its serializable members.
+        /// </exception>
+        /// <exception cref="InvalidOperationException">
+        /// The <see cref="JsonSerializerContext.GetTypeInfo(Type)"/> method of the provided
+        /// <paramref name="context"/> returns <see langword="null"/> for the type to convert.
+        /// </exception>
+        /// <exception cref="ArgumentNullException">
+        /// <paramref name="inputType"/> or <paramref name="context"/> is <see langword="null"/>.
+        /// </exception>
+        public static JsonDocument SerializeToDocument(object? value, Type inputType, JsonSerializerContext context)
+        {
+            if (context == null)
+            {
+                throw new ArgumentNullException(nameof(context));
+            }
+
+            Type runtimeType = GetRuntimeTypeAndValidateInputType(value, inputType);
+            return WriteDocument(value, GetTypeInfo(context, runtimeType));
+        }
+
+        [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)]
+        private static JsonDocument WriteDocument<TValue>(in TValue value, Type runtimeType, JsonSerializerOptions? options)
+        {
+            JsonTypeInfo typeInfo = GetTypeInfo(runtimeType, options);
+            return WriteDocument(value, typeInfo);
+        }
+
+        private static JsonDocument WriteDocument<TValue>(in TValue value, JsonTypeInfo jsonTypeInfo)
+        {
+            JsonSerializerOptions options = jsonTypeInfo.Options;
+            Debug.Assert(options != null);
+
+            // For performance, share the same buffer across serialization and deserialization.
+            // The PooledByteBufferWriter is cleared and returned when JsonDocument.Dispose() is called.
+            PooledByteBufferWriter output = new(options.DefaultBufferSize);
+            using (Utf8JsonWriter writer = new(output, options.GetWriterOptions()))
+            {
+                WriteUsingMetadata(writer, value, jsonTypeInfo);
+            }
+
+            return JsonDocument.ParseRented(output, options.GetDocumentOptions());
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Element.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Element.cs
new file mode 100644 (file)
index 0000000..a606fee
--- /dev/null
@@ -0,0 +1,125 @@
+// 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;
+using System.Text.Json.Serialization;
+using System.Text.Json.Serialization.Metadata;
+
+namespace System.Text.Json
+{
+    public static partial class JsonSerializer
+    {
+        /// <summary>
+        /// Convert the provided value into a <see cref="JsonDocument"/>.
+        /// </summary>
+        /// <returns>A <see cref="JsonDocument"/> representation of the JSON value.</returns>
+        /// <param name="value">The value to convert.</param>
+        /// <param name="options">Options to control the conversion behavior.</param>
+        /// <exception cref="NotSupportedException">
+        /// There is no compatible <see cref="System.Text.Json.Serialization.JsonConverter"/>
+        /// for <typeparamref name="TValue"/> or its serializable members.
+        /// </exception>
+        [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)]
+        public static JsonElement SerializeToElement<TValue>(TValue value, JsonSerializerOptions? options = null) =>
+            WriteElement(value, GetRuntimeType(value), options);
+
+        /// <summary>
+        /// Convert the provided value into a <see cref="JsonDocument"/>.
+        /// </summary>
+        /// <returns>A <see cref="JsonDocument"/> representation of the value.</returns>
+        /// <param name="value">The value to convert.</param>
+        /// <param name="inputType">The type of the <paramref name="value"/> to convert.</param>
+        /// <param name="options">Options to control the conversion behavior.</param>
+        /// <exception cref="ArgumentException">
+        /// <paramref name="inputType"/> is not compatible with <paramref name="value"/>.
+        /// </exception>
+        /// <exception cref="NotSupportedException">
+        /// <exception cref="ArgumentNullException">
+        /// <paramref name="inputType"/> is <see langword="null"/>.
+        /// </exception>
+        /// There is no compatible <see cref="System.Text.Json.Serialization.JsonConverter"/>
+        /// for <paramref name="inputType"/>  or its serializable members.
+        /// </exception>
+        [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)]
+        public static JsonElement SerializeToElement(object? value, Type inputType, JsonSerializerOptions? options = null) =>
+            WriteElement(
+                value,
+                GetRuntimeTypeAndValidateInputType(value, inputType),
+                options);
+
+        /// <summary>
+        /// Convert the provided value into a <see cref="JsonDocument"/>.
+        /// </summary>
+        /// <returns>A <see cref="JsonDocument"/> representation of the value.</returns>
+        /// <param name="value">The value to convert.</param>
+        /// <param name="jsonTypeInfo">Metadata about the type to convert.</param>
+        /// <exception cref="NotSupportedException">
+        /// There is no compatible <see cref="Serialization.JsonConverter"/>
+        /// for <typeparamref name="TValue"/> or its serializable members.
+        /// </exception>
+        /// <exception cref="ArgumentNullException">
+        /// <paramref name="jsonTypeInfo"/> is <see langword="null"/>.
+        /// </exception>
+        public static JsonElement SerializeToElement<TValue>(TValue value, JsonTypeInfo<TValue> jsonTypeInfo)
+        {
+            if (jsonTypeInfo == null)
+            {
+                throw new ArgumentNullException(nameof(jsonTypeInfo));
+            }
+
+            return WriteElement(value, jsonTypeInfo);
+        }
+
+        /// <summary>
+        /// Convert the provided value into a <see cref="JsonDocument"/>.
+        /// </summary>
+        /// <returns>A <see cref="JsonDocument"/> representation of the value.</returns>
+        /// <param name="value">The value to convert.</param>
+        /// <param name="inputType">The type of the <paramref name="value"/> to convert.</param>
+        /// <param name="context">A metadata provider for serializable types.</param>
+        /// <exception cref="NotSupportedException">
+        /// There is no compatible <see cref="System.Text.Json.Serialization.JsonConverter"/>
+        /// for <paramref name="inputType"/> or its serializable members.
+        /// </exception>
+        /// <exception cref="InvalidOperationException">
+        /// The <see cref="JsonSerializerContext.GetTypeInfo(Type)"/> method of the provided
+        /// <paramref name="context"/> returns <see langword="null"/> for the type to convert.
+        /// </exception>
+        /// <exception cref="ArgumentNullException">
+        /// <paramref name="inputType"/> or <paramref name="context"/> is <see langword="null"/>.
+        /// </exception>
+        public static JsonElement SerializeToElement(object? value, Type inputType, JsonSerializerContext context)
+        {
+            if (context == null)
+            {
+                throw new ArgumentNullException(nameof(context));
+            }
+
+            Type runtimeType = GetRuntimeTypeAndValidateInputType(value, inputType);
+            return WriteElement(value, GetTypeInfo(context, runtimeType));
+        }
+
+        [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)]
+        private static JsonElement WriteElement<TValue>(in TValue value, Type runtimeType, JsonSerializerOptions? options)
+        {
+            JsonTypeInfo typeInfo = GetTypeInfo(runtimeType, options);
+            return WriteElement(value, typeInfo);
+        }
+
+        private static JsonElement WriteElement<TValue>(in TValue value, JsonTypeInfo jsonTypeInfo)
+        {
+            JsonSerializerOptions options = jsonTypeInfo.Options;
+            Debug.Assert(options != null);
+
+            // For performance, share the same buffer across serialization and deserialization.
+            using var output = new PooledByteBufferWriter(options.DefaultBufferSize);
+            using (var writer = new Utf8JsonWriter(output, options.GetWriterOptions()))
+            {
+                WriteUsingMetadata(writer, value, jsonTypeInfo);
+            }
+
+            return JsonElement.ParseValue(output.WrittenMemory.Span, options.GetDocumentOptions());
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Node.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Node.cs
new file mode 100644 (file)
index 0000000..28856f0
--- /dev/null
@@ -0,0 +1,126 @@
+// 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;
+using System.Text.Json.Nodes;
+using System.Text.Json.Serialization;
+using System.Text.Json.Serialization.Metadata;
+
+namespace System.Text.Json
+{
+    public static partial class JsonSerializer
+    {
+        /// <summary>
+        /// Convert the provided value into a <see cref="JsonNode"/>.
+        /// </summary>
+        /// <returns>A <see cref="JsonNode"/> representation of the JSON value.</returns>
+        /// <param name="value">The value to convert.</param>
+        /// <param name="options">Options to control the conversion behavior.</param>
+        /// <exception cref="NotSupportedException">
+        /// There is no compatible <see cref="System.Text.Json.Serialization.JsonConverter"/>
+        /// for <typeparamref name="TValue"/> or its serializable members.
+        /// </exception>
+        [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)]
+        public static JsonNode? SerializeToNode<TValue>(TValue value, JsonSerializerOptions? options = null) =>
+            WriteNode(value, GetRuntimeType(value), options);
+
+        /// <summary>
+        /// Convert the provided value into a <see cref="JsonNode"/>.
+        /// </summary>
+        /// <returns>A <see cref="JsonNode"/> representation of the value.</returns>
+        /// <param name="value">The value to convert.</param>
+        /// <param name="inputType">The type of the <paramref name="value"/> to convert.</param>
+        /// <param name="options">Options to control the conversion behavior.</param>
+        /// <exception cref="ArgumentException">
+        /// <paramref name="inputType"/> is not compatible with <paramref name="value"/>.
+        /// </exception>
+        /// <exception cref="NotSupportedException">
+        /// <exception cref="ArgumentNullException">
+        /// <paramref name="inputType"/> is <see langword="null"/>.
+        /// </exception>
+        /// There is no compatible <see cref="System.Text.Json.Serialization.JsonConverter"/>
+        /// for <paramref name="inputType"/>  or its serializable members.
+        /// </exception>
+        [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)]
+        public static JsonNode? SerializeToNode(object? value, Type inputType, JsonSerializerOptions? options = null) =>
+            WriteNode(
+                value,
+                GetRuntimeTypeAndValidateInputType(value, inputType),
+                options);
+
+        /// <summary>
+        /// Convert the provided value into a <see cref="JsonNode"/>.
+        /// </summary>
+        /// <returns>A <see cref="JsonNode"/> representation of the value.</returns>
+        /// <param name="value">The value to convert.</param>
+        /// <param name="jsonTypeInfo">Metadata about the type to convert.</param>
+        /// <exception cref="NotSupportedException">
+        /// There is no compatible <see cref="Serialization.JsonConverter"/>
+        /// for <typeparamref name="TValue"/> or its serializable members.
+        /// </exception>
+        /// <exception cref="ArgumentNullException">
+        /// <paramref name="jsonTypeInfo"/> is <see langword="null"/>.
+        /// </exception>
+        public static JsonNode? SerializeToNode<TValue>(TValue value, JsonTypeInfo<TValue> jsonTypeInfo)
+        {
+            if (jsonTypeInfo == null)
+            {
+                throw new ArgumentNullException(nameof(jsonTypeInfo));
+            }
+
+            return WriteNode(value, jsonTypeInfo);
+        }
+
+        /// <summary>
+        /// Convert the provided value into a <see cref="JsonNode"/>.
+        /// </summary>
+        /// <returns>A <see cref="JsonNode"/> representation of the value.</returns>
+        /// <param name="value">The value to convert.</param>
+        /// <param name="inputType">The type of the <paramref name="value"/> to convert.</param>
+        /// <param name="context">A metadata provider for serializable types.</param>
+        /// <exception cref="NotSupportedException">
+        /// There is no compatible <see cref="System.Text.Json.Serialization.JsonConverter"/>
+        /// for <paramref name="inputType"/> or its serializable members.
+        /// </exception>
+        /// <exception cref="InvalidOperationException">
+        /// The <see cref="JsonSerializerContext.GetTypeInfo(Type)"/> method of the provided
+        /// <paramref name="context"/> returns <see langword="null"/> for the type to convert.
+        /// </exception>
+        /// <exception cref="ArgumentNullException">
+        /// <paramref name="inputType"/> or <paramref name="context"/> is <see langword="null"/>.
+        /// </exception>
+        public static JsonNode? SerializeToNode(object? value, Type inputType, JsonSerializerContext context)
+        {
+            if (context == null)
+            {
+                throw new ArgumentNullException(nameof(context));
+            }
+
+            Type runtimeType = GetRuntimeTypeAndValidateInputType(value, inputType);
+            return WriteNode(value, GetTypeInfo(context, runtimeType));
+        }
+
+        [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)]
+        private static JsonNode? WriteNode<TValue>(in TValue value, Type runtimeType, JsonSerializerOptions? options)
+        {
+            JsonTypeInfo typeInfo = GetTypeInfo(runtimeType, options);
+            return WriteNode(value, typeInfo);
+        }
+
+        private static JsonNode? WriteNode<TValue>(in TValue value, JsonTypeInfo jsonTypeInfo)
+        {
+            JsonSerializerOptions options = jsonTypeInfo.Options;
+            Debug.Assert(options != null);
+
+            // For performance, share the same buffer across serialization and deserialization.
+            using var output = new PooledByteBufferWriter(options.DefaultBufferSize);
+            using (var writer = new Utf8JsonWriter(output, options.GetWriterOptions()))
+            {
+                WriteUsingMetadata(writer, value, jsonTypeInfo);
+            }
+
+            return JsonNode.Parse(output.WrittenMemory.Span, options.GetNodeOptions());
+        }
+    }
+}
index db52ffc..62d408a 100644 (file)
@@ -43,6 +43,9 @@ namespace System.Text.Json
         /// There is no compatible <see cref="System.Text.Json.Serialization.JsonConverter"/>
         /// for <paramref name="inputType"/>  or its serializable members.
         /// </exception>
+        /// <exception cref="ArgumentNullException">
+        /// <paramref name="inputType"/> is <see langword="null"/>.
+        /// </exception>
         /// <remarks>Using a <see cref="string"/> is not as efficient as using UTF-8
         /// encoding since the implementation internally uses UTF-8. See also <see cref="SerializeToUtf8Bytes(object?, Type, JsonSerializerOptions?)"/>
         /// and <see cref="SerializeAsync(IO.Stream, object?, Type, JsonSerializerOptions?, Threading.CancellationToken)"/>.
@@ -96,6 +99,9 @@ namespace System.Text.Json
         /// The <see cref="JsonSerializerContext.GetTypeInfo(Type)"/> method of the provided
         /// <paramref name="context"/> returns <see langword="null"/> for the type to convert.
         /// </exception>
+        /// <exception cref="ArgumentNullException">
+        /// <paramref name="inputType"/> or <paramref name="context"/> is <see langword="null"/>.
+        /// </exception>
         /// <remarks>Using a <see cref="string"/> is not as efficient as using UTF-8
         /// encoding since the implementation internally uses UTF-8. See also <see cref="SerializeToUtf8Bytes(object?, Type, JsonSerializerContext)"/>
         /// and <see cref="SerializeAsync(IO.Stream, object?, Type, JsonSerializerContext, Threading.CancellationToken)"/>.
index b37f11e..49e3813 100644 (file)
@@ -643,6 +643,16 @@ namespace System.Text.Json
             _lastClass = null;
         }
 
+        internal JsonDocumentOptions GetDocumentOptions()
+        {
+            return new JsonDocumentOptions
+            {
+                AllowTrailingCommas = AllowTrailingCommas,
+                CommentHandling = ReadCommentHandling,
+                MaxDepth = MaxDepth
+            };
+        }
+
         internal JsonNodeOptions GetNodeOptions()
         {
             return new JsonNodeOptions
index eb67440..10311ba 100644 (file)
@@ -8,6 +8,12 @@ namespace System.Text.Json.Serialization.Tests
 {
     public abstract partial class JsonSerializerWrapperForString
     {
+        /// <summary>
+        /// Do the deserialize methods allow a value of 'null'.
+        /// For example, deserializing JSON to a String supports null by returning a 'null' String reference from a literal value of "null".
+        /// </summary>
+        protected internal abstract bool SupportsNullValueOnDeserialize { get; }
+
         protected internal abstract Task<string> SerializeWrapper(object value, Type inputType, JsonSerializerOptions options = null);
 
         protected internal abstract Task<string> SerializeWrapper<T>(T value, JsonSerializerOptions options = null);
index c119e7f..6f83222 100644 (file)
@@ -14,6 +14,8 @@ namespace System.Text.Json.SourceGeneration.Tests
         private readonly JsonSerializerContext _defaultContext;
         private readonly Func<JsonSerializerOptions, JsonSerializerContext> _customContextCreator;
 
+        protected internal override bool SupportsNullValueOnDeserialize => false;
+
         public StringSerializerWrapper(JsonSerializerContext defaultContext, Func<JsonSerializerOptions, JsonSerializerContext> customContextCreator)
         {
             _defaultContext = defaultContext ?? throw new ArgumentNullException(nameof(defaultContext));
diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/DomTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/DomTests.cs
new file mode 100644 (file)
index 0000000..874e87e
--- /dev/null
@@ -0,0 +1,220 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Linq;
+using System.Text.Json.Nodes;
+using Xunit;
+
+namespace System.Text.Json.Serialization.Tests
+{
+    /// <summary>
+    /// Provides basic tests for serializing To\From DOM types including JsonDocument, JsonElement and JsonNode.
+    /// </summary>
+    /// The test class <see cref="System.Text.Json.Tests.Serialization.MetadataTests"/> provides tests for the JsonTypeInfo and JsonContext permutations.
+    /// The test class <see cref="JsonSerializerApiValidation"/> provides tests for input validation.
+    public static class DomTests
+    {
+        private const string Escaped_PlusSign = "\"\\u002B\""; // A '+' sign is escaped as hex.
+
+        private class MyPoco
+        {
+            public static MyPoco Create()
+            {
+                return new MyPoco { StringProp = "Hello", IntArrayProp = new int[] { 1, 2 } };
+            }
+
+            [JsonPropertyOrder(0)]
+            public string StringProp { get; set; }
+
+            [JsonPropertyOrder(1)]
+            public int[] IntArrayProp { get; set; }
+
+            public void Verify()
+            {
+                Assert.Equal("Hello", StringProp);
+                Assert.Equal(1, IntArrayProp[0]);
+                Assert.Equal(2, IntArrayProp[1]);
+            }
+        }
+
+        public const string Json =
+            "{\"StringProp\":\"Hello\",\"IntArrayProp\":[1,2]}";
+
+        [Fact]
+        public static void JsonDocumentDeserialize_Generic()
+        {
+            using JsonDocument dom = JsonDocument.Parse(Json);
+            MyPoco obj = dom.Deserialize<MyPoco>();
+            obj.Verify();
+        }
+
+        [Fact]
+        public static void JsonDocumentDeserialize_NonGeneric()
+        {
+            using JsonDocument dom = JsonDocument.Parse(Json);
+            MyPoco obj = (MyPoco)dom.Deserialize(typeof(MyPoco));
+            obj.Verify();
+        }
+
+        [Fact]
+        public static void JsonDocumentDeserialize_Null()
+        {
+            using JsonDocument dom = JsonDocument.Parse("null");
+            MyPoco obj = dom.Deserialize<MyPoco>();
+            Assert.Null(obj);
+        }
+
+        [Fact]
+        public static void JsonElementDeserialize_Generic()
+        {
+            using JsonDocument document = JsonDocument.Parse(Json);
+            JsonElement dom = document.RootElement;
+            MyPoco obj = JsonSerializer.Deserialize<MyPoco>(dom);
+            obj.Verify();
+        }
+
+        [Fact]
+        public static void JsonElementDeserialize_NonGeneric()
+        {
+            using JsonDocument document = JsonDocument.Parse(Json);
+            JsonElement dom = document.RootElement;
+            MyPoco obj = (MyPoco)JsonSerializer.Deserialize(dom, typeof(MyPoco));
+            obj.Verify();
+        }
+
+        [Fact]
+        public static void JsonElementDeserialize_Null()
+        {
+            using JsonDocument document = JsonDocument.Parse("null");
+            JsonElement dom = document.RootElement;
+            MyPoco obj = dom.Deserialize<MyPoco>();
+            Assert.Null(obj);
+        }
+
+        [Fact]
+        public static void JsonElementDeserialize_FromChildElement()
+        {
+            using JsonDocument document = JsonDocument.Parse(Json);
+            JsonElement dom = document.RootElement.GetProperty("IntArrayProp");
+            int[] arr = JsonSerializer.Deserialize<int[]>(dom);
+            Assert.Equal(1, arr[0]);
+            Assert.Equal(2, arr[1]);
+        }
+
+        [Fact]
+        public static void JsonNodeDeserialize_Generic()
+        {
+            JsonNode dom = JsonNode.Parse(Json);
+            MyPoco obj = dom.Deserialize<MyPoco>();
+            obj.Verify();
+        }
+
+        [Fact]
+        public static void JsonNodeDeserialize_NonGeneric()
+        {
+            JsonNode dom = JsonNode.Parse(Json);
+            MyPoco obj = (MyPoco)dom.Deserialize(typeof(MyPoco));
+            obj.Verify();
+        }
+
+        [Fact]
+        public static void JsonNodeDeserialize_Null()
+        {
+            JsonNode node = null;
+            MyPoco obj = JsonSerializer.Deserialize<MyPoco>(node);
+            Assert.Null(obj);
+        }
+
+        [Fact]
+        public static void JsonElementDeserialize_FromChildNode()
+        {
+            JsonNode dom = JsonNode.Parse(Json)["IntArrayProp"];
+            int[] arr = JsonSerializer.Deserialize<int[]>(dom);
+            Assert.Equal(1, arr[0]);
+            Assert.Equal(2, arr[1]);
+        }
+
+        [Fact]
+        public static void SerializeToDocument()
+        {
+            MyPoco obj = MyPoco.Create();
+            using JsonDocument dom = JsonSerializer.SerializeToDocument(obj);
+
+            JsonElement stringProp = dom.RootElement.GetProperty("StringProp");
+            Assert.Equal(JsonValueKind.String, stringProp.ValueKind);
+            Assert.Equal("Hello", stringProp.ToString());
+
+            JsonElement[] elements = dom.RootElement.GetProperty("IntArrayProp").EnumerateArray().ToArray();
+            Assert.Equal(JsonValueKind.Number, elements[0].ValueKind);
+            Assert.Equal(1, elements[0].GetInt32());
+            Assert.Equal(JsonValueKind.Number, elements[1].ValueKind);
+            Assert.Equal(2, elements[1].GetInt32());
+        }
+
+        [Fact]
+        public static void SerializeToElement()
+        {
+            MyPoco obj = MyPoco.Create();
+            JsonElement dom = JsonSerializer.SerializeToElement(obj);
+
+            JsonElement stringProp = dom.GetProperty("StringProp");
+            Assert.Equal(JsonValueKind.String, stringProp.ValueKind);
+            Assert.Equal("Hello", stringProp.ToString());
+
+            JsonElement[] elements = dom.GetProperty("IntArrayProp").EnumerateArray().ToArray();
+            Assert.Equal(JsonValueKind.Number, elements[0].ValueKind);
+            Assert.Equal(1, elements[0].GetInt32());
+            Assert.Equal(JsonValueKind.Number, elements[1].ValueKind);
+            Assert.Equal(2, elements[1].GetInt32());
+        }
+
+        [Fact]
+        public static void SerializeToNode()
+        {
+            MyPoco obj = MyPoco.Create();
+            JsonNode dom = JsonSerializer.SerializeToNode(obj);
+
+            JsonNode stringProp = dom["StringProp"];
+            Assert.True(stringProp is JsonValue);
+            Assert.Equal("Hello", stringProp.AsValue().GetValue<string>());
+
+            JsonNode arrayProp = dom["IntArrayProp"];
+            Assert.IsType<JsonArray>(arrayProp);
+            Assert.Equal(1, arrayProp[0].AsValue().GetValue<int>());
+            Assert.Equal(2, arrayProp[1].AsValue().GetValue<int>());
+        }
+
+        [Fact]
+        public static void SerializeToDocument_WithEscaping()
+        {
+            using JsonDocument document = JsonSerializer.SerializeToDocument("+");
+            JsonElement dom = document.RootElement;
+            Assert.Equal(JsonValueKind.String, dom.ValueKind);
+            Assert.Equal(Escaped_PlusSign, dom.GetRawText());
+
+            string json = dom.Deserialize<string>();
+            Assert.Equal("+", json);
+        }
+
+        [Fact]
+        public static void SerializeToElement_WithEscaping()
+        {
+            JsonElement dom = JsonSerializer.SerializeToElement("+");
+            Assert.Equal(JsonValueKind.String, dom.ValueKind);
+            Assert.Equal(Escaped_PlusSign, dom.GetRawText());
+
+            string json = dom.Deserialize<string>();
+            Assert.Equal("+", json);
+        }
+
+        [Fact]
+        public static void SerializeToNode_WithEscaping()
+        {
+            JsonNode dom = JsonSerializer.SerializeToNode("+");
+            Assert.Equal(Escaped_PlusSign, dom.ToJsonString());
+
+            string json = dom.Deserialize<string>();
+            Assert.Equal("+", json);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonSerializerApiValidation.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonSerializerApiValidation.cs
new file mode 100644 (file)
index 0000000..0603e82
--- /dev/null
@@ -0,0 +1,108 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Text.Json.Serialization.Metadata;
+using System.Text.Json.Serialization.Tests;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace System.Text.Json.Serialization.Tests
+{
+    public class JsonSerializerApiValidation_Span : JsonSerializerApiValidation
+    {
+        public JsonSerializerApiValidation_Span() : base(JsonSerializerWrapperForString.SpanSerializer) { }
+    }
+
+    public class JsonSerializerApiValidation_String : JsonSerializerApiValidation
+    {
+        public JsonSerializerApiValidation_String() : base(JsonSerializerWrapperForString.StringSerializer) { }
+    }
+
+    public class JsonSerializerApiValidation_AsyncStream : JsonSerializerApiValidation
+    {
+        public JsonSerializerApiValidation_AsyncStream() : base(JsonSerializerWrapperForString.AsyncStreamSerializer) { }
+    }
+
+    public class JsonSerializerApiValidation_SyncStream : JsonSerializerApiValidation
+    {
+        public JsonSerializerApiValidation_SyncStream() : base(JsonSerializerWrapperForString.SyncStreamSerializer) { }
+    }
+
+    public class JsonSerializerApiValidation_Writer : JsonSerializerApiValidation
+    {
+        public JsonSerializerApiValidation_Writer() : base(JsonSerializerWrapperForString.ReaderWriterSerializer) { }
+    }
+
+    public class JsonSerializerApiValidation_Document : JsonSerializerApiValidation
+    {
+        public JsonSerializerApiValidation_Document() : base(JsonSerializerWrapperForString.DocumentSerializer) { }
+    }
+
+    public class JsonSerializerApiValidation_Element : JsonSerializerApiValidation
+    {
+        public JsonSerializerApiValidation_Element() : base(JsonSerializerWrapperForString.ElementSerializer) { }
+    }
+
+    public class JsonSerializerApiValidation_Node : JsonSerializerApiValidation
+    {
+        public JsonSerializerApiValidation_Node() : base(JsonSerializerWrapperForString.NodeSerializer) { }
+    }
+}
+
+/// <summary>
+/// Verifies input values for public JsonSerializer methods.
+/// </summary>
+public abstract class JsonSerializerApiValidation
+{
+    private class MyPoco { }
+
+    internal partial class MyDummyContext : JsonSerializerContext
+    {
+        public MyDummyContext() : base(new JsonSerializerOptions(), new JsonSerializerOptions()) { }
+        public MyDummyContext(JsonSerializerOptions options) : base(options, new JsonSerializerOptions()) { }
+        public override JsonTypeInfo? GetTypeInfo(Type type) => throw new NotImplementedException();
+    }
+
+    private JsonTypeInfo<MyPoco> myDummyTypeInfo = JsonMetadataServices.CreateObjectInfo<MyPoco>(
+        new JsonSerializerOptions(),
+        createObjectFunc: static () => throw new NotImplementedException(),
+        propInitFunc: null,
+        default,
+        serializeFunc: (Utf8JsonWriter writer, MyPoco value) => throw new NotImplementedException());
+
+    private JsonSerializerWrapperForString Serializer { get; }
+
+    public JsonSerializerApiValidation(JsonSerializerWrapperForString serializer)
+    {
+        Serializer = serializer;
+    }
+
+    [Fact]
+    public async Task DeserializeNullException()
+    {
+        await Assert.ThrowsAsync<ArgumentNullException>(async () => await Serializer.DeserializeWrapper<MyPoco>(json: "{}", jsonTypeInfo: null));
+        await Assert.ThrowsAsync<ArgumentNullException>(async () => await Serializer.DeserializeWrapper(json: "{}", type: null));
+        await Assert.ThrowsAsync<ArgumentNullException>(async () => await Serializer.DeserializeWrapper(json: "{}", type: typeof(MyPoco), context: null));
+        await Assert.ThrowsAsync<ArgumentNullException>(async () => await Serializer.DeserializeWrapper(json: "{}", type: null, context: new MyDummyContext()));
+
+        if (!Serializer.SupportsNullValueOnDeserialize)
+        {
+            await Assert.ThrowsAsync<ArgumentNullException>(async () => await Serializer.DeserializeWrapper(json: null, type: typeof(MyPoco), context: new MyDummyContext()));
+            await Assert.ThrowsAsync<ArgumentNullException>(async () => await Serializer.DeserializeWrapper(json: null, type: typeof(MyPoco)));
+            await Assert.ThrowsAsync<ArgumentNullException>(async () => await Serializer.DeserializeWrapper<MyPoco>(json: null));
+            await Assert.ThrowsAsync<ArgumentNullException>(async () => await Serializer.DeserializeWrapper<MyPoco>(json: null, jsonTypeInfo: myDummyTypeInfo));
+        }
+    }
+
+    [Fact]
+    public async Task SerializeNullException()
+    {
+        await Assert.ThrowsAsync<ArgumentNullException>(async () => await Serializer.SerializeWrapper<MyPoco>(value: new MyPoco(), jsonTypeInfo: null));
+        await Assert.ThrowsAsync<ArgumentNullException>(async () => await Serializer.SerializeWrapper(value: new MyPoco(), inputType: null));
+        await Assert.ThrowsAsync<ArgumentNullException>(async () => await Serializer.SerializeWrapper(value: new MyPoco(), inputType: typeof(MyPoco), context: null));
+        await Assert.ThrowsAsync<ArgumentNullException>(async () => await Serializer.SerializeWrapper(value: new MyPoco(), inputType: null, context: new MyDummyContext()));
+    }
+}
index 6b69100..a715e49 100644 (file)
@@ -2,6 +2,7 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System.IO;
+using System.Text.Json.Nodes;
 using System.Text.Json.Serialization.Metadata;
 using System.Threading.Tasks;
 using Xunit;
@@ -21,9 +22,14 @@ namespace System.Text.Json.Serialization.Tests
         public static JsonSerializerWrapperForString AsyncStreamSerializerWithSmallBuffer => new AsyncStreamSerializerWrapperWithSmallBuffer();
         public static JsonSerializerWrapperForString SyncStreamSerializer => new SyncStreamSerializerWrapper();
         public static JsonSerializerWrapperForString ReaderWriterSerializer => new ReaderWriterSerializerWrapper();
+        public static JsonSerializerWrapperForString DocumentSerializer => new DocumentSerializerWrapper();
+        public static JsonSerializerWrapperForString ElementSerializer => new ElementSerializerWrapper();
+        public static JsonSerializerWrapperForString NodeSerializer => new NodeSerializerWrapper();
 
         private class SpanSerializerWrapper : JsonSerializerWrapperForString
         {
+            protected internal override bool SupportsNullValueOnDeserialize => true; // a 'null' value is supported via implicit operator.
+
             protected internal override Task<string> SerializeWrapper(object value, Type inputType, JsonSerializerOptions options = null)
             {
                 byte[] result = JsonSerializer.SerializeToUtf8Bytes(value, inputType, options);
@@ -71,6 +77,8 @@ namespace System.Text.Json.Serialization.Tests
 
         private class StringSerializerWrapper : JsonSerializerWrapperForString
         {
+            protected internal override bool SupportsNullValueOnDeserialize => true;
+
             protected internal override Task<string> SerializeWrapper(object value, Type inputType, JsonSerializerOptions options = null)
             {
                 return Task.FromResult(JsonSerializer.Serialize(value, inputType, options));
@@ -114,69 +122,89 @@ namespace System.Text.Json.Serialization.Tests
 
         private class AsyncStreamSerializerWrapper : JsonSerializerWrapperForString
         {
+            protected internal override bool SupportsNullValueOnDeserialize => false;
+
             protected internal override async Task<string> SerializeWrapper(object value, Type inputType, JsonSerializerOptions options = null)
             {
-                using var stream = new MemoryStream();
+                using MemoryStream stream = new();
                 await JsonSerializer.SerializeAsync(stream, value, inputType, options);
                 return Encoding.UTF8.GetString(stream.ToArray());
             }
 
             protected internal override async Task<string> SerializeWrapper<T>(T value, JsonSerializerOptions options = null)
             {
-                using var stream = new MemoryStream();
+                using MemoryStream stream = new();
                 await JsonSerializer.SerializeAsync<T>(stream, value, options);
                 return Encoding.UTF8.GetString(stream.ToArray());
             }
 
             protected internal override async Task<string> SerializeWrapper(object value, Type inputType, JsonSerializerContext context)
             {
-                using var stream = new MemoryStream();
+                using MemoryStream stream = new();
                 await JsonSerializer.SerializeAsync(stream, value, inputType, context);
                 return Encoding.UTF8.GetString(stream.ToArray());
             }
 
             protected internal override async Task<string> SerializeWrapper<T>(T value, JsonTypeInfo<T> jsonTypeInfo)
             {
-                using var stream = new MemoryStream();
+                using MemoryStream stream = new();
                 await JsonSerializer.SerializeAsync(stream, value, jsonTypeInfo);
                 return Encoding.UTF8.GetString(stream.ToArray());
             }
 
             protected internal override async Task<T> DeserializeWrapper<T>(string json, JsonSerializerOptions options = null)
             {
-                using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(json)))
+                if (json is null)
                 {
-                    return await JsonSerializer.DeserializeAsync<T>(stream, options ?? _optionsWithSmallBuffer);
+                    // Emulate a null Stream for API validation tests.
+                    return await JsonSerializer.DeserializeAsync<T>((Stream)null, options ?? _optionsWithSmallBuffer);
                 }
+
+                using MemoryStream stream = new(Encoding.UTF8.GetBytes(json));
+                return await JsonSerializer.DeserializeAsync<T>(stream, options ?? _optionsWithSmallBuffer);
             }
 
             protected internal override async Task<object> DeserializeWrapper(string json, Type type, JsonSerializerOptions options = null)
             {
-                using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(json)))
+                if (json is null)
                 {
-                    return await JsonSerializer.DeserializeAsync(stream, type, options ?? _optionsWithSmallBuffer);
+                    // Emulate a null Stream for API validation tests.
+                    return await JsonSerializer.DeserializeAsync((Stream)null, type, options ?? _optionsWithSmallBuffer);
                 }
+
+                using MemoryStream stream = new(Encoding.UTF8.GetBytes(json));
+                return await JsonSerializer.DeserializeAsync(stream, type, options ?? _optionsWithSmallBuffer);
             }
 
             protected internal override async Task<T> DeserializeWrapper<T>(string json, JsonTypeInfo<T> jsonTypeInfo)
             {
-                using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(json)))
+                if (json is null)
                 {
-                    return await JsonSerializer.DeserializeAsync(stream, jsonTypeInfo);
+                    // Emulate a null Stream for API validation tests.
+                    return await JsonSerializer.DeserializeAsync((Stream)null, jsonTypeInfo);
                 }
+
+                using MemoryStream stream = new(Encoding.UTF8.GetBytes(json));
+                return await JsonSerializer.DeserializeAsync(stream, jsonTypeInfo);
             }
 
             protected internal override async Task<object> DeserializeWrapper(string json, Type type, JsonSerializerContext context)
             {
-                using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(json)))
+                if (json is null)
                 {
-                    return await JsonSerializer.DeserializeAsync(stream, type, context);
+                    // Emulate a null Stream for API validation tests.
+                    return await JsonSerializer.DeserializeAsync((Stream)null, type, context);
                 }
+
+                using MemoryStream stream = new(Encoding.UTF8.GetBytes(json));
+                return await JsonSerializer.DeserializeAsync(stream, type, context);
             }
         }
 
         private class AsyncStreamSerializerWrapperWithSmallBuffer : AsyncStreamSerializerWrapper
         {
+            protected internal override bool SupportsNullValueOnDeserialize => false;
+
             protected internal override Task<string> SerializeWrapper(object value, Type inputType, JsonSerializerOptions options = null)
             {
                 if (options == null)
@@ -195,98 +223,130 @@ namespace System.Text.Json.Serialization.Tests
 
         private class SyncStreamSerializerWrapper : JsonSerializerWrapperForString
         {
+            protected internal override bool SupportsNullValueOnDeserialize => false;
+
             protected internal override Task<string> SerializeWrapper(object value, Type inputType, JsonSerializerOptions options = null)
             {
-                using var stream = new MemoryStream();
+                using MemoryStream stream = new();
                 JsonSerializer.Serialize(stream, value, inputType, options);
                 return Task.FromResult(Encoding.UTF8.GetString(stream.ToArray()));
             }
 
             protected internal override Task<string> SerializeWrapper<T>(T value, JsonSerializerOptions options = null)
             {
-                using var stream = new MemoryStream();
+                using MemoryStream stream = new();
                 JsonSerializer.Serialize<T>(stream, value, options);
                 return Task.FromResult(Encoding.UTF8.GetString(stream.ToArray()));
             }
 
             protected internal override Task<string> SerializeWrapper(object value, Type inputType, JsonSerializerContext context)
             {
-                using var stream = new MemoryStream();
+                using MemoryStream stream = new();
                 JsonSerializer.Serialize(stream, value, inputType, context);
                 return Task.FromResult(Encoding.UTF8.GetString(stream.ToArray()));
             }
 
             protected internal override Task<string> SerializeWrapper<T>(T value, JsonTypeInfo<T> jsonTypeInfo)
             {
-                using var stream = new MemoryStream();
+                using MemoryStream stream = new();
                 JsonSerializer.Serialize(stream, value, jsonTypeInfo);
                 return Task.FromResult(Encoding.UTF8.GetString(stream.ToArray()));
             }
 
             protected internal override Task<T> DeserializeWrapper<T>(string json, JsonSerializerOptions options = null)
             {
-                using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(json)))
+                if (json is null)
                 {
-                    return Task.FromResult(JsonSerializer.Deserialize<T>(stream, options ?? _optionsWithSmallBuffer));
+                    // Emulate a null Stream for API validation tests.
+                    return Task.FromResult(JsonSerializer.Deserialize<T>((Stream)null, options ?? _optionsWithSmallBuffer));
                 }
+
+                using MemoryStream stream = new(Encoding.UTF8.GetBytes(json));
+                return Task.FromResult(JsonSerializer.Deserialize<T>(stream, options ?? _optionsWithSmallBuffer));
             }
 
             protected internal override Task<object> DeserializeWrapper(string json, Type type, JsonSerializerOptions options = null)
             {
-                using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(json)))
+                if (json is null)
                 {
-                    return Task.FromResult(JsonSerializer.Deserialize(stream, type, options ?? _optionsWithSmallBuffer));
+                    // Emulate a null Stream for API validation tests.
+                    return Task.FromResult(JsonSerializer.Deserialize((Stream)null, type, options ?? _optionsWithSmallBuffer));
                 }
+
+                using MemoryStream stream = new(Encoding.UTF8.GetBytes(json));
+                return Task.FromResult(JsonSerializer.Deserialize(stream, type, options ?? _optionsWithSmallBuffer));
             }
 
             protected internal override Task<T> DeserializeWrapper<T>(string json, JsonTypeInfo<T> jsonTypeInfo)
             {
-                using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(json)))
+                if (json is null)
                 {
-                    return Task.FromResult(JsonSerializer.Deserialize<T>(stream, jsonTypeInfo));
+                    // Emulate a null Stream for API validation tests.
+                    return Task.FromResult(JsonSerializer.Deserialize<T>((Stream)null, jsonTypeInfo));
                 }
+
+                using MemoryStream stream = new(Encoding.UTF8.GetBytes(json));
+                return Task.FromResult(JsonSerializer.Deserialize<T>(stream, jsonTypeInfo));
             }
 
             protected internal override Task<object> DeserializeWrapper(string json, Type type, JsonSerializerContext context)
             {
-                using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(json)))
+                if (json is null)
                 {
-                    return Task.FromResult(JsonSerializer.Deserialize(stream, type, context));
+                    // Emulate a null Stream for API validation tests.
+                    return Task.FromResult(JsonSerializer.Deserialize((Stream)null, type, context));
                 }
+
+                using MemoryStream stream = new(Encoding.UTF8.GetBytes(json));
+                return Task.FromResult(JsonSerializer.Deserialize(stream, type, context));
             }
         }
 
         private class ReaderWriterSerializerWrapper : JsonSerializerWrapperForString
         {
+            protected internal override bool SupportsNullValueOnDeserialize => false;
+
             protected internal override Task<string> SerializeWrapper(object value, Type inputType, JsonSerializerOptions options = null)
             {
                 using MemoryStream stream = new MemoryStream();
-                using var writer = new Utf8JsonWriter(stream);
-                JsonSerializer.Serialize(writer, value, inputType, options);
+                using (Utf8JsonWriter writer = new(stream))
+                {
+                    JsonSerializer.Serialize(writer, value, inputType, options);
+                }
+
                 return Task.FromResult(Encoding.UTF8.GetString(stream.ToArray()));
             }
 
             protected internal override Task<string> SerializeWrapper<T>(T value, JsonSerializerOptions options = null)
             {
                 using MemoryStream stream = new MemoryStream();
-                using var writer = new Utf8JsonWriter(stream);
-                JsonSerializer.Serialize<T>(writer, value, options);
+                using (Utf8JsonWriter writer = new(stream))
+                {
+                    JsonSerializer.Serialize<T>(writer, value, options);
+                }
+
                 return Task.FromResult(Encoding.UTF8.GetString(stream.ToArray()));
             }
 
             protected internal override Task<string> SerializeWrapper(object value, Type inputType, JsonSerializerContext context)
             {
                 using MemoryStream stream = new MemoryStream();
-                using var writer = new Utf8JsonWriter(stream);
-                JsonSerializer.Serialize(writer, value, inputType, context);
+                using (Utf8JsonWriter writer = new(stream))
+                {
+                    JsonSerializer.Serialize(writer, value, inputType, context);
+                }
+
                 return Task.FromResult(Encoding.UTF8.GetString(stream.ToArray()));
             }
 
             protected internal override Task<string> SerializeWrapper<T>(T value, JsonTypeInfo<T> jsonTypeInfo)
             {
                 using MemoryStream stream = new MemoryStream();
-                using var writer = new Utf8JsonWriter(stream);
-                JsonSerializer.Serialize(writer, value, jsonTypeInfo);
+                using (Utf8JsonWriter writer = new(stream))
+                {
+                    JsonSerializer.Serialize(writer, value, jsonTypeInfo);
+                }
+
                 return Task.FromResult(Encoding.UTF8.GetString(stream.ToArray()));
             }
 
@@ -314,5 +374,267 @@ namespace System.Text.Json.Serialization.Tests
                 return Task.FromResult(JsonSerializer.Deserialize(ref reader, type, context));
             }
         }
+
+        private class DocumentSerializerWrapper : JsonSerializerWrapperForString
+        {
+            protected internal override bool SupportsNullValueOnDeserialize => false;
+
+            protected internal override Task<string> SerializeWrapper(object value, Type inputType, JsonSerializerOptions options = null)
+            {
+                JsonDocument document = JsonSerializer.SerializeToDocument(value, inputType, options);
+                return Task.FromResult(GetStringFromDocument(document));
+            }
+
+            protected internal override Task<string> SerializeWrapper<T>(T value, JsonSerializerOptions options = null)
+            {
+                JsonDocument document = JsonSerializer.SerializeToDocument(value, options);
+                return Task.FromResult(GetStringFromDocument(document));
+            }
+
+            protected internal override Task<string> SerializeWrapper(object value, Type inputType, JsonSerializerContext context)
+            {
+                JsonDocument document = JsonSerializer.SerializeToDocument(value, inputType, context);
+                return Task.FromResult(GetStringFromDocument(document));
+            }
+
+            protected internal override Task<string> SerializeWrapper<T>(T value, JsonTypeInfo<T> jsonTypeInfo)
+            {
+                JsonDocument document = JsonSerializer.SerializeToDocument(value, jsonTypeInfo);
+                return Task.FromResult(GetStringFromDocument(document));
+            }
+
+            private string GetStringFromDocument(JsonDocument document)
+            {
+                // Emulate a null return value.
+                if (document is null)
+                {
+                    return "null";
+                }
+
+                using MemoryStream stream = new();
+                using (Utf8JsonWriter writer = new(stream))
+                {
+                    document.WriteTo(writer);
+                }
+
+                return Encoding.UTF8.GetString(stream.ToArray());
+            }
+
+            protected internal override Task<T> DeserializeWrapper<T>(string json, JsonSerializerOptions options = null)
+            {
+                if (json is null)
+                {
+                    // Emulate a null document for API validation tests.
+                    return Task.FromResult(JsonSerializer.Deserialize<T>(document: null));
+                }
+
+                using JsonDocument document = JsonDocument.Parse(json);
+                return Task.FromResult(document.Deserialize<T>(options));
+            }
+
+            protected internal override Task<object> DeserializeWrapper(string json, Type type, JsonSerializerOptions options = null)
+            {
+                if (json is null)
+                {
+                    // Emulate a null document for API validation tests.
+                    return Task.FromResult(JsonSerializer.Deserialize(document: null, type));
+                }
+
+                using JsonDocument document = JsonDocument.Parse(json);
+                return Task.FromResult(document.Deserialize(type, options));
+            }
+
+            protected internal override Task<T> DeserializeWrapper<T>(string json, JsonTypeInfo<T> jsonTypeInfo)
+            {
+                if (json is null)
+                {
+                    // Emulate a null document for API validation tests.
+                    return Task.FromResult(JsonSerializer.Deserialize<T>(document: null, jsonTypeInfo));
+                }
+
+                using JsonDocument document = JsonDocument.Parse(json);
+                return Task.FromResult(document.Deserialize<T>(jsonTypeInfo));
+            }
+
+            protected internal override Task<object> DeserializeWrapper(string json, Type type, JsonSerializerContext context)
+            {
+                if (json is null)
+                {
+                    // Emulate a null document for API validation tests.
+                    return Task.FromResult(JsonSerializer.Deserialize(document: null, type, context));
+                }
+
+                using JsonDocument document = JsonDocument.Parse(json);
+                return Task.FromResult(document.Deserialize(type, context));
+            }
+        }
+
+        private class ElementSerializerWrapper : JsonSerializerWrapperForString
+        {
+            protected internal override bool SupportsNullValueOnDeserialize => false;
+
+            protected internal override Task<string> SerializeWrapper(object value, Type inputType, JsonSerializerOptions options = null)
+            {
+                JsonElement element = JsonSerializer.SerializeToElement(value, inputType, options);
+                return Task.FromResult(GetStringFromElement(element));
+            }
+
+            protected internal override Task<string> SerializeWrapper<T>(T value, JsonSerializerOptions options = null)
+            {
+                JsonElement element = JsonSerializer.SerializeToElement(value, options);
+                return Task.FromResult(GetStringFromElement(element));
+            }
+
+            protected internal override Task<string> SerializeWrapper(object value, Type inputType, JsonSerializerContext context)
+            {
+                JsonElement element = JsonSerializer.SerializeToElement(value, inputType, context);
+                return Task.FromResult(GetStringFromElement(element));
+            }
+
+            protected internal override Task<string> SerializeWrapper<T>(T value, JsonTypeInfo<T> jsonTypeInfo)
+            {
+                JsonElement element = JsonSerializer.SerializeToElement(value, jsonTypeInfo);
+                return Task.FromResult(GetStringFromElement(element));
+            }
+
+            private string GetStringFromElement(JsonElement element)
+            {
+                using MemoryStream stream = new MemoryStream();
+                using (Utf8JsonWriter writer = new(stream))
+                {
+                    element.WriteTo(writer);
+                }
+                return Encoding.UTF8.GetString(stream.ToArray());
+            }
+
+            protected internal override Task<T> DeserializeWrapper<T>(string json, JsonSerializerOptions options = null)
+            {
+                using JsonDocument document = JsonDocument.Parse(json);
+                return Task.FromResult(document.RootElement.Deserialize<T>(options));
+            }
+
+            protected internal override Task<object> DeserializeWrapper(string json, Type type, JsonSerializerOptions options = null)
+            {
+                using JsonDocument document = JsonDocument.Parse(json);
+                return Task.FromResult(document.RootElement.Deserialize(type, options));
+            }
+
+            protected internal override Task<T> DeserializeWrapper<T>(string json, JsonTypeInfo<T> jsonTypeInfo)
+            {
+                using JsonDocument document = JsonDocument.Parse(json);
+                return Task.FromResult(document.RootElement.Deserialize<T>(jsonTypeInfo));
+            }
+
+            protected internal override Task<object> DeserializeWrapper(string json, Type type, JsonSerializerContext context)
+            {
+                using JsonDocument document = JsonDocument.Parse(json);
+                return Task.FromResult(document.RootElement.Deserialize(type, context));
+            }
+        }
+
+        private class NodeSerializerWrapper : JsonSerializerWrapperForString
+        {
+            protected internal override bool SupportsNullValueOnDeserialize => true;
+
+            protected internal override Task<string> SerializeWrapper(object value, Type inputType, JsonSerializerOptions options = null)
+            {
+                JsonNode node = JsonSerializer.SerializeToNode(value, inputType, options);
+
+                // Emulate a null return value.
+                if (node is null)
+                {
+                    return Task.FromResult("null");
+                }
+
+                return Task.FromResult(node.ToJsonString());
+            }
+
+            protected internal override Task<string> SerializeWrapper<T>(T value, JsonSerializerOptions options = null)
+            {
+                JsonNode node = JsonSerializer.SerializeToNode(value, options);
+
+                // Emulate a null return value.
+                if (node is null)
+                {
+                    return Task.FromResult("null");
+                }
+
+                return Task.FromResult(node.ToJsonString());
+            }
+
+            protected internal override Task<string> SerializeWrapper(object value, Type inputType, JsonSerializerContext context)
+            {
+                JsonNode node = JsonSerializer.SerializeToNode(value, inputType, context);
+
+                // Emulate a null return value.
+                if (node is null)
+                {
+                    return Task.FromResult("null");
+                }
+
+                return Task.FromResult(node.ToJsonString());
+            }
+
+            protected internal override Task<string> SerializeWrapper<T>(T value, JsonTypeInfo<T> jsonTypeInfo)
+            {
+                JsonNode node = JsonSerializer.SerializeToNode(value, jsonTypeInfo);
+
+                // Emulate a null return value.
+                if (node is null)
+                {
+                    return Task.FromResult("null");
+                }
+
+                return Task.FromResult(node.ToJsonString());
+            }
+
+            protected internal override Task<T> DeserializeWrapper<T>(string json, JsonSerializerOptions options = null)
+            {
+                if (json is null)
+                {
+                    // Emulate a null node for API validation tests.
+                    return Task.FromResult(JsonSerializer.Deserialize<T>(node: null));
+                }
+
+                JsonNode node = JsonNode.Parse(json);
+                return Task.FromResult(node.Deserialize<T>(options));
+            }
+
+            protected internal override Task<object> DeserializeWrapper(string json, Type type, JsonSerializerOptions options = null)
+            {
+                if (json is null)
+                {
+                    // Emulate a null node for API validation tests.
+                    return Task.FromResult(JsonSerializer.Deserialize(node: null, type));
+                }
+
+                JsonNode node = JsonNode.Parse(json);
+                return Task.FromResult(node.Deserialize(type, options));
+            }
+
+            protected internal override Task<T> DeserializeWrapper<T>(string json, JsonTypeInfo<T> jsonTypeInfo)
+            {
+                if (json is null)
+                {
+                    // Emulate a null node for API validation tests.
+                    return Task.FromResult(JsonSerializer.Deserialize(node: null, jsonTypeInfo));
+                }
+
+                JsonNode node = JsonNode.Parse(json);
+                return Task.FromResult(node.Deserialize<T>(jsonTypeInfo));
+            }
+
+            protected internal override Task<object> DeserializeWrapper(string json, Type type, JsonSerializerContext context)
+            {
+                if (json is null)
+                {
+                    // Emulate a null document for API validation tests.
+                    return Task.FromResult(JsonSerializer.Deserialize(node: null, type));
+                }
+
+                JsonNode node = JsonNode.Parse(json);
+                return Task.FromResult(node.Deserialize(type, context));
+            }
+        }
     }
 }
index 0630ae2..98f8ba2 100644 (file)
@@ -32,6 +32,21 @@ namespace System.Text.Json.Serialization.Tests
         public MetadataTests_LowLevel() : base(JsonSerializerWrapperForString.ReaderWriterSerializer) { }
     }
 
+    public class MetadataTests_Document : MetadataTests
+    {
+        public MetadataTests_Document() : base(JsonSerializerWrapperForString.DocumentSerializer) { }
+    }
+
+    public class MetadataTests_Element : MetadataTests
+    {
+        public MetadataTests_Element() : base(JsonSerializerWrapperForString.ElementSerializer) { }
+    }
+
+    public class MetadataTests_Node : MetadataTests
+    {
+        public MetadataTests_Node() : base(JsonSerializerWrapperForString.NodeSerializer) { }
+    }
+
     public abstract partial class MetadataTests
     {
         protected JsonSerializerWrapperForString Serializer { get; }
index ada72d4..d89b36a 100644 (file)
@@ -1644,6 +1644,21 @@ namespace System.Text.Json.Serialization.Tests
         public NumberHandlingTests_SyncOverload() : base(JsonSerializerWrapperForString.StringSerializer) { }
     }
 
+    public class NumberHandlingTests_Document : NumberHandlingTests_OverloadSpecific
+    {
+        public NumberHandlingTests_Document() : base(JsonSerializerWrapperForString.DocumentSerializer) { }
+    }
+
+    public class NumberHandlingTests_Element : NumberHandlingTests_OverloadSpecific
+    {
+        public NumberHandlingTests_Element() : base(JsonSerializerWrapperForString.ElementSerializer) { }
+    }
+
+    public class NumberHandlingTests_Node : NumberHandlingTests_OverloadSpecific
+    {
+        public NumberHandlingTests_Node() : base(JsonSerializerWrapperForString.NodeSerializer) { }
+    }
+
     public abstract class NumberHandlingTests_OverloadSpecific
     {
         private JsonSerializerWrapperForString Deserializer { get; }
index dfd167d..26bec2f 100644 (file)
@@ -39,6 +39,21 @@ namespace System.Text.Json.Serialization.Tests
         public PolymorphicTests_Writer() : base(JsonSerializerWrapperForString.ReaderWriterSerializer) { }
     }
 
+    public class PolymorphicTests_Document : PolymorphicTests
+    {
+        public PolymorphicTests_Document() : base(JsonSerializerWrapperForString.DocumentSerializer) { }
+    }
+
+    public class PolymorphicTests_Element : PolymorphicTests
+    {
+        public PolymorphicTests_Element() : base(JsonSerializerWrapperForString.ElementSerializer) { }
+    }
+
+    public class PolymorphicTests_Node : PolymorphicTests
+    {
+        public PolymorphicTests_Node() : base(JsonSerializerWrapperForString.NodeSerializer) { }
+    }
+
     public abstract class PolymorphicTests
     {
         private JsonSerializerWrapperForString Serializer { get; }
index a3d45fd..0f7121c 100644 (file)
     <Compile Include="Serialization\CustomConverterTests\CustomConverterTests.ValueTypedMember.cs" />
     <Compile Include="Serialization\CustomConverterTests\CustomConverterTests.cs" />
     <Compile Include="Serialization\CyclicTests.cs" />
+    <Compile Include="Serialization\DomTests.cs" />
     <Compile Include="Serialization\DynamicTests.cs" />
     <Compile Include="Serialization\EnumConverterTests.cs" />
     <Compile Include="Serialization\EnumTests.cs" />
     <Compile Include="Serialization\InvalidTypeTests.cs" />
     <Compile Include="Serialization\JsonDocumentTests.cs" />
     <Compile Include="Serialization\JsonElementTests.cs" />
+    <Compile Include="Serialization\JsonSerializerApiValidation.cs" />
     <Compile Include="Serialization\JsonSerializerWrapperForStream.cs" />
     <Compile Include="Serialization\JsonSerializerWrapperForString.cs" />
     <Compile Include="Serialization\MetadataTests\MetadataTests.cs" />