From a76d816cf7f55a018d6ffa0a3d9b0aaee81f4bc0 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Mon, 27 Nov 2023 16:37:06 +0000 Subject: [PATCH 1/2] Add JSON null support for the built-in (ReadOnly)Memory converters. --- .../Converters/Collection/MemoryConverter.cs | 18 ++++++++++++++++++ .../Collection/ReadOnlyMemoryConverter.cs | 18 ++++++++++++++++++ .../JsonMetadataServicesConverter.cs | 11 ++++++++++- .../Converters/Value/MemoryByteConverter.cs | 4 +++- .../Value/ReadOnlyMemoryByteConverter.cs | 4 +++- .../Serialization/JsonResumableConverterOfT.cs | 4 ++-- .../CollectionTests/CollectionTests.Memory.cs | 17 ++++++++++++++++- 7 files changed, 70 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/MemoryConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/MemoryConverter.cs index 55f145e535479..44640af29ec57 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/MemoryConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/MemoryConverter.cs @@ -2,12 +2,30 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; namespace System.Text.Json.Serialization.Converters { internal sealed class MemoryConverter : JsonCollectionConverter, T> { internal override bool CanHaveMetadata => false; + public override bool HandleNull => true; + + internal override bool OnTryRead( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options, + scoped ref ReadStack state, + [MaybeNullWhen(false)] out Memory value) + { + if (reader.TokenType is JsonTokenType.Null) + { + value = default; + return true; + } + + return base.OnTryRead(ref reader, typeToConvert, options, ref state, out value); + } protected override void Add(in T value, ref ReadStack state) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ReadOnlyMemoryConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ReadOnlyMemoryConverter.cs index b8778c7736baa..177f1accf7418 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ReadOnlyMemoryConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ReadOnlyMemoryConverter.cs @@ -2,12 +2,30 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; namespace System.Text.Json.Serialization.Converters { internal sealed class ReadOnlyMemoryConverter : JsonCollectionConverter, T> { internal override bool CanHaveMetadata => false; + public override bool HandleNull => true; + + internal override bool OnTryRead( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options, + scoped ref ReadStack state, + [MaybeNullWhen(false)] out ReadOnlyMemory value) + { + if (reader.TokenType is JsonTokenType.Null) + { + value = default; + return true; + } + + return base.OnTryRead(ref reader, typeToConvert, options, ref state, out value); + } protected override void Add(in T value, ref ReadStack state) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonMetadataServicesConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonMetadataServicesConverter.cs index debb3f0d77c09..88303c6284d1f 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonMetadataServicesConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonMetadataServicesConverter.cs @@ -20,6 +20,7 @@ internal sealed class JsonMetadataServicesConverter : JsonResumableConverter< internal override Type? KeyType => Converter.KeyType; internal override Type? ElementType => Converter.ElementType; + public override bool HandleNull { get; } internal override bool ConstructorIsParameterized => Converter.ConstructorIsParameterized; internal override bool SupportsCreateObjectDelegate => Converter.SupportsCreateObjectDelegate; @@ -29,8 +30,16 @@ internal sealed class JsonMetadataServicesConverter : JsonResumableConverter< public JsonMetadataServicesConverter(JsonConverter converter) { - ConverterStrategy = converter.ConverterStrategy; Converter = converter; + ConverterStrategy = converter.ConverterStrategy; + IsInternalConverter = converter.IsInternalConverter; + IsInternalConverterForNumberType = converter.IsInternalConverterForNumberType; + CanBePolymorphic = converter.CanBePolymorphic; + + // Ensure HandleNull values reflect the exact configuration of the source converter + HandleNullOnRead = converter.HandleNullOnRead; + HandleNullOnWrite = converter.HandleNullOnWrite; + HandleNull = converter.HandleNullOnWrite; } internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, scoped ref ReadStack state, out T? value) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/MemoryByteConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/MemoryByteConverter.cs index 20536c81b3627..0efa7e71a13ac 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/MemoryByteConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/MemoryByteConverter.cs @@ -5,9 +5,11 @@ namespace System.Text.Json.Serialization.Converters { internal sealed class MemoryByteConverter : JsonConverter> { + public override bool HandleNull => true; + public override Memory Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return reader.GetBytesFromBase64(); + return reader.TokenType is JsonTokenType.Null ? default : reader.GetBytesFromBase64(); } public override void Write(Utf8JsonWriter writer, Memory value, JsonSerializerOptions options) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/ReadOnlyMemoryByteConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/ReadOnlyMemoryByteConverter.cs index 68b7c1f033354..48eddafefba20 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/ReadOnlyMemoryByteConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/ReadOnlyMemoryByteConverter.cs @@ -5,9 +5,11 @@ namespace System.Text.Json.Serialization.Converters { internal sealed class ReadOnlyMemoryByteConverter : JsonConverter> { + public override bool HandleNull => true; + public override ReadOnlyMemory Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return reader.GetBytesFromBase64(); + return reader.TokenType is JsonTokenType.Null ? default : reader.GetBytesFromBase64(); } public override void Write(Utf8JsonWriter writer, ReadOnlyMemory value, JsonSerializerOptions options) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonResumableConverterOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonResumableConverterOfT.cs index fcf782a2e4fe7..81bceda8d8404 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonResumableConverterOfT.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonResumableConverterOfT.cs @@ -12,6 +12,8 @@ namespace System.Text.Json.Serialization /// internal abstract class JsonResumableConverter : JsonConverter { + public override bool HandleNull => false; + public sealed override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (options is null) @@ -51,7 +53,5 @@ public sealed override void Write(Utf8JsonWriter writer, T value, JsonSerializer throw; } } - - public sealed override bool HandleNull => false; } } diff --git a/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.Memory.cs b/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.Memory.cs index bf6e148cde18c..58525300624be 100644 --- a/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.Memory.cs +++ b/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.Memory.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Text.Json.Tests; using System.Threading.Tasks; using Xunit; @@ -104,6 +103,22 @@ public async Task DeserializeMemoryByteAsync() AssertExtensions.SequenceEqual(s_testData, readOnlyMemory.Span); } + [Fact] + public async Task DeserializeNullAsMemory() + { + ReadOnlyMemory readOnlyMemOfInt = await Serializer.DeserializeWrapper>("null"); + Assert.True(readOnlyMemOfInt.IsEmpty); + + Memory memOfInt = await Serializer.DeserializeWrapper>("null"); + Assert.True(memOfInt.IsEmpty); + + ReadOnlyMemory readOnlyMemOfByte = await Serializer.DeserializeWrapper>("null"); + Assert.True(readOnlyMemOfByte.IsEmpty); + + Memory memOfByte = await Serializer.DeserializeWrapper>("null"); + Assert.True(memOfByte.IsEmpty); + } + [Fact] public async Task SerializeMemoryByteClassAsync() { From dec00601605cc5f679109332392e5aa1e125b283 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Mon, 27 Nov 2023 16:41:32 +0000 Subject: [PATCH 2/2] Address feedback --- .../Json/Serialization/Converters/Collection/MemoryConverter.cs | 2 +- .../Converters/Collection/ReadOnlyMemoryConverter.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/MemoryConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/MemoryConverter.cs index 44640af29ec57..670ab037367a8 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/MemoryConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/MemoryConverter.cs @@ -16,7 +16,7 @@ internal override bool OnTryRead( Type typeToConvert, JsonSerializerOptions options, scoped ref ReadStack state, - [MaybeNullWhen(false)] out Memory value) + out Memory value) { if (reader.TokenType is JsonTokenType.Null) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ReadOnlyMemoryConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ReadOnlyMemoryConverter.cs index 177f1accf7418..8ca1ef71db3fb 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ReadOnlyMemoryConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ReadOnlyMemoryConverter.cs @@ -16,7 +16,7 @@ internal override bool OnTryRead( Type typeToConvert, JsonSerializerOptions options, scoped ref ReadStack state, - [MaybeNullWhen(false)] out ReadOnlyMemory value) + out ReadOnlyMemory value) { if (reader.TokenType is JsonTokenType.Null) {