From ddc729f8f772210d69906b91371b44de8528976a Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Tue, 27 Jul 2021 20:35:54 -0500 Subject: [PATCH] Add interop between serializer and DOMs (#56112) --- .../System.Text.Json/ref/System.Text.Json.cs | 36 ++ .../System.Text.Json/src/System.Text.Json.csproj | 6 + .../Text/Json/Document/JsonDocument.Parse.cs | 22 +- .../src/System/Text/Json/Document/JsonDocument.cs | 64 +++- .../src/System/Text/Json/Document/JsonElement.cs | 7 + .../src/System/Text/Json/Nodes/JsonNode.To.cs | 24 +- .../Converters/Node/JsonNodeConverter.cs | 1 + .../Serialization/JsonSerializer.Read.Document.cs | 173 +++++++++ .../Serialization/JsonSerializer.Read.Element.cs | 140 ++++++++ .../Json/Serialization/JsonSerializer.Read.Node.cs | 157 +++++++++ .../Serialization/JsonSerializer.Read.String.cs | 4 +- .../JsonSerializer.Write.ByteArray.cs | 12 +- .../Serialization/JsonSerializer.Write.Document.cs | 126 +++++++ .../Serialization/JsonSerializer.Write.Element.cs | 125 +++++++ .../Serialization/JsonSerializer.Write.Node.cs | 126 +++++++ .../Serialization/JsonSerializer.Write.String.cs | 6 + .../Json/Serialization/JsonSerializerOptions.cs | 10 + .../tests/Common/JsonSerializerWrapperForString.cs | 6 + .../Serialization/JsonSerializerWrapper.cs | 2 + .../Serialization/DomTests.cs | 220 ++++++++++++ .../Serialization/JsonSerializerApiValidation.cs | 108 ++++++ .../JsonSerializerWrapperForString.cs | 386 +++++++++++++++++++-- .../Serialization/MetadataTests/MetadataTests.cs | 15 + .../Serialization/NumberHandlingTests.cs | 15 + .../Serialization/PolymorphicTests.cs | 15 + .../System.Text.Json.Tests.csproj | 2 + 26 files changed, 1734 insertions(+), 74 deletions(-) create mode 100644 src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Document.cs create mode 100644 src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Element.cs create mode 100644 src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Node.cs create mode 100644 src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Document.cs create mode 100644 src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Element.cs create mode 100644 src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Node.cs create mode 100644 src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/DomTests.cs create mode 100644 src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonSerializerApiValidation.cs diff --git a/src/libraries/System.Text.Json/ref/System.Text.Json.cs b/src/libraries/System.Text.Json/ref/System.Text.Json.cs index c376591..99a987c 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs @@ -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(string json, System.Text.Json.JsonSerializerOptions? options = null) { throw null; } public static TValue? Deserialize(string json, System.Text.Json.Serialization.Metadata.JsonTypeInfo 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(this System.Text.Json.JsonDocument document, System.Text.Json.JsonSerializerOptions? options = null) { throw null; } + public static TValue? Deserialize(this System.Text.Json.JsonDocument document, System.Text.Json.Serialization.Metadata.JsonTypeInfo 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(this System.Text.Json.JsonElement element, System.Text.Json.JsonSerializerOptions? options = null) { throw null; } + public static TValue? Deserialize(this System.Text.Json.JsonElement element, System.Text.Json.Serialization.Metadata.JsonTypeInfo 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(this System.Text.Json.Nodes.JsonNode? node, System.Text.Json.JsonSerializerOptions? options = null) { throw null; } + public static TValue? Deserialize(this System.Text.Json.Nodes.JsonNode? node, System.Text.Json.Serialization.Metadata.JsonTypeInfo 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(ref System.Text.Json.Utf8JsonReader reader, System.Text.Json.Serialization.Metadata.JsonTypeInfo 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(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(System.IO.Stream utf8Json, TValue value, System.Text.Json.Serialization.Metadata.JsonTypeInfo 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 value, System.Text.Json.JsonSerializerOptions? options = null) { throw null; } + public static System.Text.Json.JsonDocument SerializeToDocument(TValue value, System.Text.Json.Serialization.Metadata.JsonTypeInfo 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 value, System.Text.Json.JsonSerializerOptions? options = null) { throw null; } + public static System.Text.Json.JsonElement SerializeToElement(TValue value, System.Text.Json.Serialization.Metadata.JsonTypeInfo 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 value, System.Text.Json.JsonSerializerOptions? options = null) { throw null; } + public static System.Text.Json.Nodes.JsonNode? SerializeToNode(TValue value, System.Text.Json.Serialization.Metadata.JsonTypeInfo 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.")] diff --git a/src/libraries/System.Text.Json/src/System.Text.Json.csproj b/src/libraries/System.Text.Json/src/System.Text.Json.csproj index 5a9ea18..031229d 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -99,6 +99,12 @@ + + + + + + diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.Parse.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.Parse.cs index 8df0c30..c287240 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.Parse.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.Parse.cs @@ -46,7 +46,7 @@ namespace System.Text.Json /// public static JsonDocument Parse(ReadOnlyMemory utf8Json, JsonDocumentOptions options = default) { - return Parse(utf8Json, options.GetReaderOptions(), null); + return Parse(utf8Json, options.GetReaderOptions()); } /// @@ -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 utf8Json, JsonReaderOptions readerOptions, - byte[]? extraRentedBytes) + byte[]? extraRentedArrayPoolBytes = null, + PooledByteBufferWriter? extraPooledByteBufferWriter = null) { ReadOnlySpan 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 ReadToEnd(Stream stream) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.cs index 26eabb0..2cb1321 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.cs @@ -23,7 +23,13 @@ namespace System.Text.Json { private ReadOnlyMemory _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 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)); } /// @@ -63,14 +83,22 @@ namespace System.Text.Json _parsedData.Dispose(); _utf8Json = ReadOnlyMemory.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.Shared.Return(extraRentedBytes); + } + } + else if (_hasExtraPooledByteBufferWriter) { - extraRentedBytes.AsSpan(0, length).Clear(); - ArrayPool.Shared.Return(extraRentedBytes); + PooledByteBufferWriter? extraBufferWriter = Interlocked.Exchange(ref _extraPooledByteBufferWriter, null); + extraBufferWriter?.Dispose(); } } @@ -184,7 +212,12 @@ namespace System.Text.Json return endIndex; } - private ReadOnlyMemory GetRawValue(int index, bool includeQuotes) + internal ReadOnlyMemory GetRootRawValue() + { + return GetRawValue(0, includeQuotes : true); + } + + internal ReadOnlyMemory GetRawValue(int index, bool includeQuotes) { CheckNotDisposed(); @@ -764,7 +797,12 @@ namespace System.Text.Json ReadOnlyMemory 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; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.cs index 18e69d8..5f30dd6 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.cs @@ -1169,6 +1169,13 @@ namespace System.Text.Json return _parent.GetRawValueAsString(_idx); } + internal ReadOnlyMemory GetRawValue() + { + CheckValidInstance(); + + return _parent.GetRawValue(_idx, includeQuotes: true); + } + internal string GetPropertyRawText() { CheckValidInstance(); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.To.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.To.cs index cbc3934..2abaaf0 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.To.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.To.cs @@ -12,15 +12,13 @@ namespace System.Text.Json.Nodes /// JSON representation of current instance. 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()); } /// @@ -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()); } /// diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonNodeConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonNodeConverter.cs index 07da2c9..8d0f7bb 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonNodeConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonNodeConverter.cs @@ -28,6 +28,7 @@ namespace System.Text.Json.Serialization.Converters if (value == null) { writer.WriteNullValue(); + // Note JsonSerializer.Deserialize(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 index 0000000..45f78a9 --- /dev/null +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Document.cs @@ -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 + { + /// + /// Converts the representing a single JSON value into a . + /// + /// A representation of the JSON value. + /// The to convert. + /// Options to control the behavior during parsing. + /// + /// is . + /// + /// + /// is not compatible with the JSON. + /// + /// + /// There is no compatible + /// for or its serializable members. + /// + [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)] + public static TValue? Deserialize(this JsonDocument document, JsonSerializerOptions? options = null) + { + if (document == null) + { + throw new ArgumentNullException(nameof(document)); + } + + return ReadUsingOptions(document, typeof(TValue), options); + } + + /// + /// Converts the representing a single JSON value into a . + /// + /// A representation of the JSON value. + /// The to convert. + /// The type of the object to convert to and return. + /// Options to control the behavior during parsing. + /// + /// or is . + /// + /// + /// is not compatible with the JSON. + /// + /// + /// There is no compatible + /// for or its serializable members. + /// + [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(document, returnType, options); + } + + /// + /// Converts the representing a single JSON value into a . + /// + /// A representation of the JSON value. + /// The to convert. + /// Metadata about the type to convert. + /// + /// is . + /// + /// -or- + /// + /// is . + /// + /// + /// is not compatible with the JSON. + /// + /// + /// There is no compatible + /// for or its serializable members. + /// + public static TValue? Deserialize(this JsonDocument document, JsonTypeInfo jsonTypeInfo) + { + if (document == null) + { + throw new ArgumentNullException(nameof(document)); + } + + if (jsonTypeInfo == null) + { + throw new ArgumentNullException(nameof(jsonTypeInfo)); + } + + return ReadUsingMetadata(document, jsonTypeInfo); + } + + /// + /// Converts the representing a single JSON value into a . + /// + /// A representation of the JSON value. + /// The to convert. + /// The type of the object to convert to and return. + /// A metadata provider for serializable types. + /// + /// is . + /// + /// -or- + /// + /// is . + /// + /// -or- + /// + /// is . + /// + /// + /// The JSON is invalid. + /// + /// -or- + /// + /// is not compatible with the JSON. + /// + /// -or- + /// + /// There is remaining data in the string beyond a single JSON value. + /// + /// There is no compatible + /// for or its serializable members. + /// + /// + /// The method of the provided + /// returns for the type to convert. + /// + 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(document, GetTypeInfo(context, returnType)); + } + + [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)] + private static TValue? ReadUsingOptions(JsonDocument document, Type returnType, JsonSerializerOptions? options) => + ReadUsingMetadata(document, GetTypeInfo(returnType, options)); + + private static TValue? ReadUsingMetadata(JsonDocument document, JsonTypeInfo jsonTypeInfo) + { + ReadOnlySpan utf8Json = document.GetRootRawValue().Span; + return ReadUsingMetadata(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 index 0000000..9419f64 --- /dev/null +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Element.cs @@ -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 + { + /// + /// Converts the representing a single JSON value into a . + /// + /// A representation of the JSON value. + /// The to convert. + /// Options to control the behavior during parsing. + /// + /// is not compatible with the JSON. + /// + /// + /// There is no compatible + /// for or its serializable members. + /// + [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)] + public static TValue? Deserialize(this JsonElement element, JsonSerializerOptions? options = null) => + ReadUsingOptions(element, typeof(TValue), options); + + /// + /// Converts the representing a single JSON value into a . + /// + /// A representation of the JSON value. + /// The to convert. + /// The type of the object to convert to and return. + /// Options to control the behavior during parsing. + /// + /// is . + /// + /// + /// is not compatible with the JSON. + /// + /// + /// There is no compatible + /// for or its serializable members. + /// + [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)] + public static object? Deserialize(this JsonElement element, Type returnType, JsonSerializerOptions? options = null) + { + if (returnType == null) + { + throw new ArgumentNullException(nameof(returnType)); + } + + return ReadUsingOptions(element, returnType, options); + } + + /// + /// Converts the representing a single JSON value into a . + /// + /// A representation of the JSON value. + /// The to convert. + /// Metadata about the type to convert. + /// + /// is . + /// + /// + /// is not compatible with the JSON. + /// + /// + /// There is no compatible + /// for or its serializable members. + /// + public static TValue? Deserialize(this JsonElement element, JsonTypeInfo jsonTypeInfo) + { + if (jsonTypeInfo == null) + { + throw new ArgumentNullException(nameof(jsonTypeInfo)); + } + + return ReadUsingMetadata(element, jsonTypeInfo); + } + + /// + /// Converts the representing a single JSON value into a . + /// + /// A representation of the JSON value. + /// The to convert. + /// The type of the object to convert to and return. + /// A metadata provider for serializable types. + /// + /// is . + /// + /// -or- + /// + /// is . + /// + /// + /// The JSON is invalid. + /// + /// -or- + /// + /// is not compatible with the JSON. + /// + /// -or- + /// + /// There is remaining data in the string beyond a single JSON value. + /// + /// There is no compatible + /// for or its serializable members. + /// + /// + /// The method of the provided + /// returns for the type to convert. + /// + 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(element, GetTypeInfo(context, returnType)); + } + + [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)] + private static TValue? ReadUsingOptions(JsonElement element, Type returnType, JsonSerializerOptions? options) => + ReadUsingMetadata(element, GetTypeInfo(returnType, options)); + + private static TValue? ReadUsingMetadata(JsonElement element, JsonTypeInfo jsonTypeInfo) + { + ReadOnlySpan utf8Json = element.GetRawValue().Span; + return ReadUsingMetadata(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 index 0000000..32de1de --- /dev/null +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Node.cs @@ -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 + { + /// + /// Converts the representing a single JSON value into a . + /// + /// A representation of the JSON value. + /// The to convert. + /// Options to control the behavior during parsing. + /// + /// is not compatible with the JSON. + /// + /// + /// There is no compatible + /// for or its serializable members. + /// + [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)] + public static TValue? Deserialize(this JsonNode? node, JsonSerializerOptions? options = null) + { + return ReadUsingOptions(node, typeof(TValue), options); + } + + /// + /// Converts the representing a single JSON value into a . + /// + /// A representation of the JSON value. + /// The to convert. + /// The type of the object to convert to and return. + /// Options to control the behavior during parsing. + /// + /// is not compatible with the JSON. + /// + /// + /// There is no compatible + /// for or its serializable members. + /// + [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)] + public static object? Deserialize(this JsonNode? node, Type returnType, JsonSerializerOptions? options = null) + { + if (returnType == null) + { + throw new ArgumentNullException(nameof(returnType)); + } + + return ReadUsingOptions(node, returnType, options); + } + + /// + /// Converts the representing a single JSON value into a . + /// + /// A representation of the JSON value. + /// The to convert. + /// Metadata about the type to convert. + /// + /// is . + /// + /// + /// is not compatible with the JSON. + /// + /// + /// There is no compatible + /// for or its serializable members. + /// + public static TValue? Deserialize(this JsonNode? node, JsonTypeInfo jsonTypeInfo) + { + if (jsonTypeInfo == null) + { + throw new ArgumentNullException(nameof(jsonTypeInfo)); + } + + return ReadUsingMetadata(node, jsonTypeInfo); + } + + /// + /// Converts the representing a single JSON value into a . + /// + /// A representation of the JSON value. + /// The to convert. + /// The type of the object to convert to and return. + /// A metadata provider for serializable types. + /// + /// is . + /// + /// -or- + /// + /// is . + /// + /// + /// The JSON is invalid. + /// + /// -or- + /// + /// is not compatible with the JSON. + /// + /// -or- + /// + /// There is remaining data in the string beyond a single JSON value. + /// + /// There is no compatible + /// for or its serializable members. + /// + /// + /// The method of the provided + /// returns for the type to convert. + /// + 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(node, GetTypeInfo(context, returnType)); + } + + [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)] + private static TValue? ReadUsingOptions(JsonNode? node, Type returnType, JsonSerializerOptions? options) => + ReadUsingMetadata(node, GetTypeInfo(returnType, options)); + + private static TValue? ReadUsingMetadata(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(output.WrittenMemory.Span, jsonTypeInfo); + } + } +} diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.String.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.String.cs index 29e06ef..50add92 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.String.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.String.cs @@ -262,7 +262,7 @@ namespace System.Text.Json /// The type of the object to convert to and return. /// A metadata provider for serializable types. /// - /// is . + /// or is . /// /// -or- /// @@ -307,7 +307,7 @@ namespace System.Text.Json /// The type of the object to convert to and return. /// A metadata provider for serializable types. /// - /// is . + /// or is . /// /// -or- /// diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.ByteArray.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.ByteArray.cs index b64bcea..880ce55 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.ByteArray.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.ByteArray.cs @@ -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 index 0000000..1c5d7c7 --- /dev/null +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Document.cs @@ -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 + { + /// + /// Convert the provided value into a . + /// + /// A representation of the JSON value. + /// The value to convert. + /// Options to control the conversion behavior. + /// + /// There is no compatible + /// for or its serializable members. + /// + [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)] + public static JsonDocument SerializeToDocument(TValue value, JsonSerializerOptions? options = null) => + WriteDocument(value, GetRuntimeType(value), options); + + /// + /// Convert the provided value into a . + /// + /// A representation of the value. + /// The value to convert. + /// The type of the to convert. + /// Options to control the conversion behavior. + /// + /// is not compatible with . + /// + /// + /// + /// is . + /// + /// There is no compatible + /// for or its serializable members. + /// + [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)] + public static JsonDocument SerializeToDocument(object? value, Type inputType, JsonSerializerOptions? options = null) => + WriteDocument( + value, + GetRuntimeTypeAndValidateInputType(value, inputType), + options); + + /// + /// Convert the provided value into a . + /// + /// A representation of the value. + /// The value to convert. + /// Metadata about the type to convert. + /// + /// There is no compatible + /// for or its serializable members. + /// + /// + /// is . + /// + public static JsonDocument SerializeToDocument(TValue value, JsonTypeInfo jsonTypeInfo) + { + if (jsonTypeInfo == null) + { + throw new ArgumentNullException(nameof(jsonTypeInfo)); + } + + return WriteDocument(value, jsonTypeInfo); + } + + /// + /// Convert the provided value into a . + /// + /// A representation of the value. + /// The value to convert. + /// The type of the to convert. + /// A metadata provider for serializable types. + /// + /// There is no compatible + /// for or its serializable members. + /// + /// + /// The method of the provided + /// returns for the type to convert. + /// + /// + /// or is . + /// + 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(in TValue value, Type runtimeType, JsonSerializerOptions? options) + { + JsonTypeInfo typeInfo = GetTypeInfo(runtimeType, options); + return WriteDocument(value, typeInfo); + } + + private static JsonDocument WriteDocument(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 index 0000000..a606fee --- /dev/null +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Element.cs @@ -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 + { + /// + /// Convert the provided value into a . + /// + /// A representation of the JSON value. + /// The value to convert. + /// Options to control the conversion behavior. + /// + /// There is no compatible + /// for or its serializable members. + /// + [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)] + public static JsonElement SerializeToElement(TValue value, JsonSerializerOptions? options = null) => + WriteElement(value, GetRuntimeType(value), options); + + /// + /// Convert the provided value into a . + /// + /// A representation of the value. + /// The value to convert. + /// The type of the to convert. + /// Options to control the conversion behavior. + /// + /// is not compatible with . + /// + /// + /// + /// is . + /// + /// There is no compatible + /// for or its serializable members. + /// + [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)] + public static JsonElement SerializeToElement(object? value, Type inputType, JsonSerializerOptions? options = null) => + WriteElement( + value, + GetRuntimeTypeAndValidateInputType(value, inputType), + options); + + /// + /// Convert the provided value into a . + /// + /// A representation of the value. + /// The value to convert. + /// Metadata about the type to convert. + /// + /// There is no compatible + /// for or its serializable members. + /// + /// + /// is . + /// + public static JsonElement SerializeToElement(TValue value, JsonTypeInfo jsonTypeInfo) + { + if (jsonTypeInfo == null) + { + throw new ArgumentNullException(nameof(jsonTypeInfo)); + } + + return WriteElement(value, jsonTypeInfo); + } + + /// + /// Convert the provided value into a . + /// + /// A representation of the value. + /// The value to convert. + /// The type of the to convert. + /// A metadata provider for serializable types. + /// + /// There is no compatible + /// for or its serializable members. + /// + /// + /// The method of the provided + /// returns for the type to convert. + /// + /// + /// or is . + /// + 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(in TValue value, Type runtimeType, JsonSerializerOptions? options) + { + JsonTypeInfo typeInfo = GetTypeInfo(runtimeType, options); + return WriteElement(value, typeInfo); + } + + private static JsonElement WriteElement(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 index 0000000..28856f0 --- /dev/null +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Node.cs @@ -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 + { + /// + /// Convert the provided value into a . + /// + /// A representation of the JSON value. + /// The value to convert. + /// Options to control the conversion behavior. + /// + /// There is no compatible + /// for or its serializable members. + /// + [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)] + public static JsonNode? SerializeToNode(TValue value, JsonSerializerOptions? options = null) => + WriteNode(value, GetRuntimeType(value), options); + + /// + /// Convert the provided value into a . + /// + /// A representation of the value. + /// The value to convert. + /// The type of the to convert. + /// Options to control the conversion behavior. + /// + /// is not compatible with . + /// + /// + /// + /// is . + /// + /// There is no compatible + /// for or its serializable members. + /// + [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)] + public static JsonNode? SerializeToNode(object? value, Type inputType, JsonSerializerOptions? options = null) => + WriteNode( + value, + GetRuntimeTypeAndValidateInputType(value, inputType), + options); + + /// + /// Convert the provided value into a . + /// + /// A representation of the value. + /// The value to convert. + /// Metadata about the type to convert. + /// + /// There is no compatible + /// for or its serializable members. + /// + /// + /// is . + /// + public static JsonNode? SerializeToNode(TValue value, JsonTypeInfo jsonTypeInfo) + { + if (jsonTypeInfo == null) + { + throw new ArgumentNullException(nameof(jsonTypeInfo)); + } + + return WriteNode(value, jsonTypeInfo); + } + + /// + /// Convert the provided value into a . + /// + /// A representation of the value. + /// The value to convert. + /// The type of the to convert. + /// A metadata provider for serializable types. + /// + /// There is no compatible + /// for or its serializable members. + /// + /// + /// The method of the provided + /// returns for the type to convert. + /// + /// + /// or is . + /// + 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(in TValue value, Type runtimeType, JsonSerializerOptions? options) + { + JsonTypeInfo typeInfo = GetTypeInfo(runtimeType, options); + return WriteNode(value, typeInfo); + } + + private static JsonNode? WriteNode(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()); + } + } +} diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.String.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.String.cs index db52ffc..62d408a 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.String.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.String.cs @@ -43,6 +43,9 @@ namespace System.Text.Json /// There is no compatible /// for or its serializable members. /// + /// + /// is . + /// /// Using a is not as efficient as using UTF-8 /// encoding since the implementation internally uses UTF-8. See also /// and . @@ -96,6 +99,9 @@ namespace System.Text.Json /// The method of the provided /// returns for the type to convert. /// + /// + /// or is . + /// /// Using a is not as efficient as using UTF-8 /// encoding since the implementation internally uses UTF-8. See also /// and . diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs index b37f11e..49e3813 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs @@ -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 diff --git a/src/libraries/System.Text.Json/tests/Common/JsonSerializerWrapperForString.cs b/src/libraries/System.Text.Json/tests/Common/JsonSerializerWrapperForString.cs index eb67440..10311ba 100644 --- a/src/libraries/System.Text.Json/tests/Common/JsonSerializerWrapperForString.cs +++ b/src/libraries/System.Text.Json/tests/Common/JsonSerializerWrapperForString.cs @@ -8,6 +8,12 @@ namespace System.Text.Json.Serialization.Tests { public abstract partial class JsonSerializerWrapperForString { + /// + /// 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". + /// + protected internal abstract bool SupportsNullValueOnDeserialize { get; } + protected internal abstract Task SerializeWrapper(object value, Type inputType, JsonSerializerOptions options = null); protected internal abstract Task SerializeWrapper(T value, JsonSerializerOptions options = null); diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/JsonSerializerWrapper.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/JsonSerializerWrapper.cs index c119e7f..6f83222 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/JsonSerializerWrapper.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/JsonSerializerWrapper.cs @@ -14,6 +14,8 @@ namespace System.Text.Json.SourceGeneration.Tests private readonly JsonSerializerContext _defaultContext; private readonly Func _customContextCreator; + protected internal override bool SupportsNullValueOnDeserialize => false; + public StringSerializerWrapper(JsonSerializerContext defaultContext, Func 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 index 0000000..874e87e --- /dev/null +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/DomTests.cs @@ -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 +{ + /// + /// Provides basic tests for serializing To\From DOM types including JsonDocument, JsonElement and JsonNode. + /// + /// The test class provides tests for the JsonTypeInfo and JsonContext permutations. + /// The test class 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(); + 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(); + Assert.Null(obj); + } + + [Fact] + public static void JsonElementDeserialize_Generic() + { + using JsonDocument document = JsonDocument.Parse(Json); + JsonElement dom = document.RootElement; + MyPoco obj = JsonSerializer.Deserialize(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(); + 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(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(); + 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(node); + Assert.Null(obj); + } + + [Fact] + public static void JsonElementDeserialize_FromChildNode() + { + JsonNode dom = JsonNode.Parse(Json)["IntArrayProp"]; + int[] arr = JsonSerializer.Deserialize(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()); + + JsonNode arrayProp = dom["IntArrayProp"]; + Assert.IsType(arrayProp); + Assert.Equal(1, arrayProp[0].AsValue().GetValue()); + Assert.Equal(2, arrayProp[1].AsValue().GetValue()); + } + + [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(); + 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(); + Assert.Equal("+", json); + } + + [Fact] + public static void SerializeToNode_WithEscaping() + { + JsonNode dom = JsonSerializer.SerializeToNode("+"); + Assert.Equal(Escaped_PlusSign, dom.ToJsonString()); + + string json = dom.Deserialize(); + 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 index 0000000..0603e82 --- /dev/null +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonSerializerApiValidation.cs @@ -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) { } + } +} + +/// +/// Verifies input values for public JsonSerializer methods. +/// +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 myDummyTypeInfo = JsonMetadataServices.CreateObjectInfo( + 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(async () => await Serializer.DeserializeWrapper(json: "{}", jsonTypeInfo: null)); + await Assert.ThrowsAsync(async () => await Serializer.DeserializeWrapper(json: "{}", type: null)); + await Assert.ThrowsAsync(async () => await Serializer.DeserializeWrapper(json: "{}", type: typeof(MyPoco), context: null)); + await Assert.ThrowsAsync(async () => await Serializer.DeserializeWrapper(json: "{}", type: null, context: new MyDummyContext())); + + if (!Serializer.SupportsNullValueOnDeserialize) + { + await Assert.ThrowsAsync(async () => await Serializer.DeserializeWrapper(json: null, type: typeof(MyPoco), context: new MyDummyContext())); + await Assert.ThrowsAsync(async () => await Serializer.DeserializeWrapper(json: null, type: typeof(MyPoco))); + await Assert.ThrowsAsync(async () => await Serializer.DeserializeWrapper(json: null)); + await Assert.ThrowsAsync(async () => await Serializer.DeserializeWrapper(json: null, jsonTypeInfo: myDummyTypeInfo)); + } + } + + [Fact] + public async Task SerializeNullException() + { + await Assert.ThrowsAsync(async () => await Serializer.SerializeWrapper(value: new MyPoco(), jsonTypeInfo: null)); + await Assert.ThrowsAsync(async () => await Serializer.SerializeWrapper(value: new MyPoco(), inputType: null)); + await Assert.ThrowsAsync(async () => await Serializer.SerializeWrapper(value: new MyPoco(), inputType: typeof(MyPoco), context: null)); + await Assert.ThrowsAsync(async () => await Serializer.SerializeWrapper(value: new MyPoco(), inputType: null, context: new MyDummyContext())); + } +} diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonSerializerWrapperForString.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonSerializerWrapperForString.cs index 6b69100..a715e49 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonSerializerWrapperForString.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonSerializerWrapperForString.cs @@ -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 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 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 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 SerializeWrapper(T value, JsonSerializerOptions options = null) { - using var stream = new MemoryStream(); + using MemoryStream stream = new(); await JsonSerializer.SerializeAsync(stream, value, options); return Encoding.UTF8.GetString(stream.ToArray()); } protected internal override async Task 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 SerializeWrapper(T value, JsonTypeInfo 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 DeserializeWrapper(string json, JsonSerializerOptions options = null) { - using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(json))) + if (json is null) { - return await JsonSerializer.DeserializeAsync(stream, options ?? _optionsWithSmallBuffer); + // Emulate a null Stream for API validation tests. + return await JsonSerializer.DeserializeAsync((Stream)null, options ?? _optionsWithSmallBuffer); } + + using MemoryStream stream = new(Encoding.UTF8.GetBytes(json)); + return await JsonSerializer.DeserializeAsync(stream, options ?? _optionsWithSmallBuffer); } protected internal override async Task 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 DeserializeWrapper(string json, JsonTypeInfo 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 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 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 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 SerializeWrapper(T value, JsonSerializerOptions options = null) { - using var stream = new MemoryStream(); + using MemoryStream stream = new(); JsonSerializer.Serialize(stream, value, options); return Task.FromResult(Encoding.UTF8.GetString(stream.ToArray())); } protected internal override Task 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 SerializeWrapper(T value, JsonTypeInfo 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 DeserializeWrapper(string json, JsonSerializerOptions options = null) { - using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(json))) + if (json is null) { - return Task.FromResult(JsonSerializer.Deserialize(stream, options ?? _optionsWithSmallBuffer)); + // Emulate a null Stream for API validation tests. + return Task.FromResult(JsonSerializer.Deserialize((Stream)null, options ?? _optionsWithSmallBuffer)); } + + using MemoryStream stream = new(Encoding.UTF8.GetBytes(json)); + return Task.FromResult(JsonSerializer.Deserialize(stream, options ?? _optionsWithSmallBuffer)); } protected internal override Task 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 DeserializeWrapper(string json, JsonTypeInfo jsonTypeInfo) { - using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(json))) + if (json is null) { - return Task.FromResult(JsonSerializer.Deserialize(stream, jsonTypeInfo)); + // Emulate a null Stream for API validation tests. + return Task.FromResult(JsonSerializer.Deserialize((Stream)null, jsonTypeInfo)); } + + using MemoryStream stream = new(Encoding.UTF8.GetBytes(json)); + return Task.FromResult(JsonSerializer.Deserialize(stream, jsonTypeInfo)); } protected internal override Task 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 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 SerializeWrapper(T value, JsonSerializerOptions options = null) { using MemoryStream stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream); - JsonSerializer.Serialize(writer, value, options); + using (Utf8JsonWriter writer = new(stream)) + { + JsonSerializer.Serialize(writer, value, options); + } + return Task.FromResult(Encoding.UTF8.GetString(stream.ToArray())); } protected internal override Task 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 SerializeWrapper(T value, JsonTypeInfo 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 SerializeWrapper(object value, Type inputType, JsonSerializerOptions options = null) + { + JsonDocument document = JsonSerializer.SerializeToDocument(value, inputType, options); + return Task.FromResult(GetStringFromDocument(document)); + } + + protected internal override Task SerializeWrapper(T value, JsonSerializerOptions options = null) + { + JsonDocument document = JsonSerializer.SerializeToDocument(value, options); + return Task.FromResult(GetStringFromDocument(document)); + } + + protected internal override Task SerializeWrapper(object value, Type inputType, JsonSerializerContext context) + { + JsonDocument document = JsonSerializer.SerializeToDocument(value, inputType, context); + return Task.FromResult(GetStringFromDocument(document)); + } + + protected internal override Task SerializeWrapper(T value, JsonTypeInfo 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 DeserializeWrapper(string json, JsonSerializerOptions options = null) + { + if (json is null) + { + // Emulate a null document for API validation tests. + return Task.FromResult(JsonSerializer.Deserialize(document: null)); + } + + using JsonDocument document = JsonDocument.Parse(json); + return Task.FromResult(document.Deserialize(options)); + } + + protected internal override Task 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 DeserializeWrapper(string json, JsonTypeInfo jsonTypeInfo) + { + if (json is null) + { + // Emulate a null document for API validation tests. + return Task.FromResult(JsonSerializer.Deserialize(document: null, jsonTypeInfo)); + } + + using JsonDocument document = JsonDocument.Parse(json); + return Task.FromResult(document.Deserialize(jsonTypeInfo)); + } + + protected internal override Task 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 SerializeWrapper(object value, Type inputType, JsonSerializerOptions options = null) + { + JsonElement element = JsonSerializer.SerializeToElement(value, inputType, options); + return Task.FromResult(GetStringFromElement(element)); + } + + protected internal override Task SerializeWrapper(T value, JsonSerializerOptions options = null) + { + JsonElement element = JsonSerializer.SerializeToElement(value, options); + return Task.FromResult(GetStringFromElement(element)); + } + + protected internal override Task SerializeWrapper(object value, Type inputType, JsonSerializerContext context) + { + JsonElement element = JsonSerializer.SerializeToElement(value, inputType, context); + return Task.FromResult(GetStringFromElement(element)); + } + + protected internal override Task SerializeWrapper(T value, JsonTypeInfo 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 DeserializeWrapper(string json, JsonSerializerOptions options = null) + { + using JsonDocument document = JsonDocument.Parse(json); + return Task.FromResult(document.RootElement.Deserialize(options)); + } + + protected internal override Task 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 DeserializeWrapper(string json, JsonTypeInfo jsonTypeInfo) + { + using JsonDocument document = JsonDocument.Parse(json); + return Task.FromResult(document.RootElement.Deserialize(jsonTypeInfo)); + } + + protected internal override Task 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 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 SerializeWrapper(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 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 SerializeWrapper(T value, JsonTypeInfo 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 DeserializeWrapper(string json, JsonSerializerOptions options = null) + { + if (json is null) + { + // Emulate a null node for API validation tests. + return Task.FromResult(JsonSerializer.Deserialize(node: null)); + } + + JsonNode node = JsonNode.Parse(json); + return Task.FromResult(node.Deserialize(options)); + } + + protected internal override Task 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 DeserializeWrapper(string json, JsonTypeInfo 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(jsonTypeInfo)); + } + + protected internal override Task 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)); + } + } } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/MetadataTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/MetadataTests.cs index 0630ae2..98f8ba2 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/MetadataTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/MetadataTests.cs @@ -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; } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/NumberHandlingTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/NumberHandlingTests.cs index ada72d4..d89b36a 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/NumberHandlingTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/NumberHandlingTests.cs @@ -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; } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.cs index dfd167d..26bec2f 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.cs @@ -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; } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj index a3d45fd..0f7121c 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj @@ -132,6 +132,7 @@ + @@ -141,6 +142,7 @@ + -- 2.7.4