From: Huo Yaoyuan Date: Wed, 14 Oct 2020 15:19:24 +0000 (+0800) Subject: Add ReadOnlySpan overloads to JsonSerializer.Deserialize (#41957) X-Git-Tag: submit/tizen/20210909.063632~5082 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=7ca6d85188e3352935d33b2202f2de8d253cc419;p=platform%2Fupstream%2Fdotnet%2Fruntime.git Add ReadOnlySpan overloads to JsonSerializer.Deserialize (#41957) * Add char span overloads to JsonSerializer.Deserialize. * Add new methods to ref source. * Fix ambiguous call in test. * Fix documentation issue. * Guard and test for empty span. * Add span in deserialization test wrapper. * Apply doc suggestions Co-authored-by: Layomi Akinrinade * Treat default span as empty, and cover returnType in test. * Apply doc suggestions Co-authored-by: Ahson Khan * Add exception doc for null returnType. Co-authored-by: Layomi Akinrinade Co-authored-by: Ahson Khan --- 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 f8fce13..14b3955 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs @@ -185,12 +185,14 @@ namespace System.Text.Json public static partial class JsonSerializer { public static object? Deserialize(System.ReadOnlySpan utf8Json, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicFields | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)] System.Type returnType, System.Text.Json.JsonSerializerOptions? options = null) { throw null; } + public static object? Deserialize(System.ReadOnlySpan json, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicFields | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)] System.Type returnType, System.Text.Json.JsonSerializerOptions? options = null) { throw null; } public static object? Deserialize(string json, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicFields | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)] System.Type returnType, System.Text.Json.JsonSerializerOptions? options = null) { throw null; } public static object? Deserialize(ref System.Text.Json.Utf8JsonReader reader, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicFields | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)] System.Type returnType, System.Text.Json.JsonSerializerOptions? options = null) { throw null; } public static System.Threading.Tasks.ValueTask DeserializeAsync(System.IO.Stream utf8Json, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicFields | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)] System.Type returnType, System.Text.Json.JsonSerializerOptions? options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public static System.Threading.Tasks.ValueTask DeserializeAsync<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicFields | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)] TValue>(System.IO.Stream utf8Json, System.Text.Json.JsonSerializerOptions? options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public static TValue? Deserialize<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicFields | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)] TValue>(System.ReadOnlySpan utf8Json, System.Text.Json.JsonSerializerOptions? options = null) { throw null; } public static TValue? Deserialize<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicFields | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)] TValue>(string json, System.Text.Json.JsonSerializerOptions? options = null) { throw null; } + public static TValue? Deserialize<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicFields | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)] TValue>(System.ReadOnlySpan json, System.Text.Json.JsonSerializerOptions? options = null) { throw null; } public static TValue? Deserialize<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicFields | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)] TValue>(ref System.Text.Json.Utf8JsonReader reader, System.Text.Json.JsonSerializerOptions? options = null) { throw null; } public static string Serialize(object? value, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicFields | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)] System.Type inputType, System.Text.Json.JsonSerializerOptions? options = null) { throw null; } public static void Serialize(System.Text.Json.Utf8JsonWriter writer, object? value, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicFields | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)] System.Type inputType, System.Text.Json.JsonSerializerOptions? options = null) { } 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 af1f35d..2fe4f81 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 @@ -23,10 +23,15 @@ namespace System.Text.Json /// is . /// /// - /// Thrown when the JSON is invalid, - /// is not compatible with the JSON, - /// or when there is remaining data in the Stream. - /// + /// 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. @@ -41,6 +46,36 @@ namespace System.Text.Json throw new ArgumentNullException(nameof(json)); } + return Deserialize(json.AsSpan(), typeof(TValue), options); + } + + /// + /// Parses the text representing a single JSON value into an instance of the type specified by a generic type parameter. + /// + /// A representation of the JSON value. + /// The JSON text to parse. + /// Options to control the behavior during parsing. + /// + /// The JSON is invalid. + /// + /// -or- + /// + /// is not compatible with the JSON. + /// + /// -or- + /// + /// There is remaining data in the span beyond a single JSON value. + /// + /// There is no compatible + /// for or its serializable members. + /// + /// Using a UTF-16 span is not as efficient as using the + /// UTF-8 methods since the implementation natively uses UTF-8. + /// + public static TValue? Deserialize<[DynamicallyAccessedMembers(MembersAccessedOnRead)] TValue>(ReadOnlySpan json, JsonSerializerOptions? options = null) + { + // default/null span is treated as empty + return Deserialize(json, typeof(TValue), options); } @@ -55,10 +90,15 @@ namespace System.Text.Json /// or is . /// /// - /// Thrown when the JSON is invalid, - /// the is not compatible with the JSON, - /// or when there is remaining data in the Stream. - /// + /// 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. @@ -78,12 +118,53 @@ namespace System.Text.Json throw new ArgumentNullException(nameof(returnType)); } + object? value = Deserialize(json.AsSpan(), returnType, options)!; + + return value; + } + + /// + /// Parse the text representing a single JSON value into an instance of a specified type. + /// + /// A representation of the JSON value. + /// The JSON text to parse. + /// The type of the object to convert to and return. + /// Options to control the behavior during parsing. + /// + /// is . + /// + /// + /// The JSON is invalid. + /// + /// -or- + /// + /// is not compatible with the JSON. + /// + /// -or- + /// + /// There is remaining data in the span beyond a single JSON value. + /// + /// There is no compatible + /// for or its serializable members. + /// + /// Using a UTF-16 span is not as efficient as using the + /// UTF-8 methods since the implementation natively uses UTF-8. + /// + public static object? Deserialize(ReadOnlySpan json, [DynamicallyAccessedMembers(MembersAccessedOnRead)] Type returnType, JsonSerializerOptions? options = null) + { + // default/null span is treated as empty + + if (returnType == null) + { + throw new ArgumentNullException(nameof(returnType)); + } + object? value = Deserialize(json, returnType, options)!; return value; } - private static TValue? Deserialize(string json, Type returnType, JsonSerializerOptions? options) + private static TValue? Deserialize(ReadOnlySpan json, Type returnType, JsonSerializerOptions? options) { const long ArrayPoolMaxSizeBeforeUsingNormalAlloc = 1024 * 1024; @@ -100,11 +181,11 @@ namespace System.Text.Json tempArray = ArrayPool.Shared.Rent(json.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) : // Use a normal alloc since the pool would create a normal alloc anyway based on the threshold (per current implementation) // and by using a normal alloc we can avoid the Clear(). - new byte[JsonReaderHelper.GetUtf8ByteCount(json.AsSpan())]; + new byte[JsonReaderHelper.GetUtf8ByteCount(json)]; try { - int actualByteCount = JsonReaderHelper.GetUtf8FromText(json.AsSpan(), utf8); + int actualByteCount = JsonReaderHelper.GetUtf8FromText(json, utf8); utf8 = utf8.Slice(0, actualByteCount); var readerState = new JsonReaderState(options.GetReaderOptions()); diff --git a/src/libraries/System.Text.Json/tests/Serialization/ConstructorTests/ConstructorTests.ParameterMatching.cs b/src/libraries/System.Text.Json/tests/Serialization/ConstructorTests/ConstructorTests.ParameterMatching.cs index 91c224d..4b28b57 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/ConstructorTests/ConstructorTests.ParameterMatching.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/ConstructorTests/ConstructorTests.ParameterMatching.cs @@ -18,6 +18,11 @@ namespace System.Text.Json.Serialization.Tests public ConstructorTests_Stream() : base(DeserializationWrapper.StreamDeserializer) { } } + public class ConstructorTests_Span : ConstructorTests + { + public ConstructorTests_Span() : base(DeserializationWrapper.SpanDeserializer) { } + } + public abstract partial class ConstructorTests { private DeserializationWrapper Serializer { get; } diff --git a/src/libraries/System.Text.Json/tests/Serialization/DeserializationWrapper.cs b/src/libraries/System.Text.Json/tests/Serialization/DeserializationWrapper.cs index b20b6f5..1c84c0b 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/DeserializationWrapper.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/DeserializationWrapper.cs @@ -15,6 +15,7 @@ namespace System.Text.Json.Serialization.Tests public static DeserializationWrapper StringDeserializer => new StringDeserializerWrapper(); public static DeserializationWrapper StreamDeserializer => new StreamDeserializerWrapper(); + public static DeserializationWrapper SpanDeserializer => new SpanDesearializationWrapper(); protected internal abstract Task DeserializeWrapper(string json, JsonSerializerOptions options = null); @@ -61,5 +62,18 @@ namespace System.Text.Json.Serialization.Tests } } } + + private class SpanDesearializationWrapper : DeserializationWrapper + { + protected internal override Task DeserializeWrapper(string json, JsonSerializerOptions options = null) + { + return Task.FromResult(JsonSerializer.Deserialize(json.AsSpan(), options)); + } + + protected internal override Task DeserializeWrapper(string json, Type type, JsonSerializerOptions options = null) + { + return Task.FromResult(JsonSerializer.Deserialize(json.AsSpan(), type, options)); + } + } } } diff --git a/src/libraries/System.Text.Json/tests/Serialization/ReadValueTests.cs b/src/libraries/System.Text.Json/tests/Serialization/ReadValueTests.cs index 16d7f1e..c29c401 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/ReadValueTests.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/ReadValueTests.cs @@ -23,6 +23,9 @@ namespace System.Text.Json.Serialization.Tests ex = Assert.Throws(() => JsonSerializer.Deserialize("", returnType: null)); Assert.Contains("returnType", ex.ToString()); + ex = Assert.Throws(() => JsonSerializer.Deserialize(new char[] { '1' }, returnType: null)); + Assert.Contains("returnType", ex.ToString()); + ex = Assert.Throws(() => JsonSerializer.Deserialize(new byte[] { 1 }, returnType: null)); Assert.Contains("returnType", ex.ToString()); @@ -33,7 +36,7 @@ namespace System.Text.Json.Serialization.Tests [Fact] public static void NullJsonThrows() { - ArgumentNullException ex = Assert.Throws(() => JsonSerializer.Deserialize(json: null, returnType: typeof(string))); + ArgumentNullException ex = Assert.Throws(() => JsonSerializer.Deserialize(json: (string)null, returnType: typeof(string))); Assert.Contains("json", ex.ToString()); ex = Assert.Throws(() => JsonSerializer.DeserializeAsync(utf8Json: null, returnType: null));