Improve serialization error message when IsReflectionEnabledByDefault is turned off...
authorEirik Tsarpalis <eirik.tsarpalis@gmail.com>
Thu, 27 Jul 2023 20:16:12 +0000 (21:16 +0100)
committerGitHub <noreply@github.com>
Thu, 27 Jul 2023 20:16:12 +0000 (21:16 +0100)
* Improve serialization error message when IsReflectionEnabledByDefault is turned off.

* Update trimming tests

src/libraries/System.Text.Json/src/Resources/Strings.resx
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoResolver.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoResolverChain.cs
src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs
src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.AsyncEnumerable.cs
src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/OptionsTests.cs
src/libraries/System.Text.Json/tests/System.Text.Json.Tests/TrimmingTests/IsReflectionEnabledByDefaultFalse.cs

index 8e1da07..83468d9 100644 (file)
   <data name="JsonSerializerOptionsNoTypeInfoResolverSpecified" xml:space="preserve">
     <value>JsonSerializerOptions instance must specify a TypeInfoResolver setting before being marked as read-only.</value>
   </data>
+  <data name="JsonSerializerIsReflectionDisabled" xml:space="preserve">
+    <value>Reflection-based serialization has been disabled for this application. Either use the source generator APIs or explicitly configure the 'JsonSerializerOptions.TypeInfoResolver' property.</value>
+  </data>
   <data name="JsonPolymorphismOptionsAssociatedWithDifferentJsonTypeInfo" xml:space="preserve">
     <value>Parameter already associated with a different JsonTypeInfo instance.</value>
   </data>
index 72b3e42..f421f23 100644 (file)
@@ -746,6 +746,10 @@ namespace System.Text.Json
                         break;
                 }
             }
+            else if (_typeInfoResolver is null or EmptyJsonTypeInfoResolver)
+            {
+                ThrowHelper.ThrowInvalidOperationException_JsonSerializerIsReflectionDisabled();
+            }
 
             MakeReadOnly();
             _isConfiguredForJsonSerializer = true;
index 59c259f..2163296 100644 (file)
@@ -68,7 +68,7 @@ namespace System.Text.Json.Serialization.Metadata
         /// <summary>
         /// Gets a resolver that returns null <see cref="JsonTypeInfo"/> for every type.
         /// </summary>
-        internal static IJsonTypeInfoResolver Empty { get; } = new JsonTypeInfoResolverChain();
+        internal static IJsonTypeInfoResolver Empty { get; } = new EmptyJsonTypeInfoResolver();
 
         /// <summary>
         /// Indicates whether the metadata generated by the current resolver
@@ -79,6 +79,15 @@ namespace System.Text.Json.Serialization.Metadata
     }
 
     /// <summary>
+    /// A <see cref="IJsonTypeInfoResolver"/> that returns null for all inputs.
+    /// </summary>
+    internal sealed class EmptyJsonTypeInfoResolver : IJsonTypeInfoResolver, IBuiltInJsonTypeInfoResolver
+    {
+        public JsonTypeInfo? GetTypeInfo(Type type, JsonSerializerOptions options) => null;
+        public bool IsCompatibleWithOptions(JsonSerializerOptions _) => true;
+    }
+
+    /// <summary>
     /// Implemented by the built-in converters to avoid rooting
     /// unused resolver dependencies in the context of the trimmer.
     /// </summary>
index f3eaa0f..446ce3e 100644 (file)
@@ -28,7 +28,7 @@ namespace System.Text.Json.Serialization.Metadata
         {
             switch (resolver)
             {
-                case null:
+                case null or EmptyJsonTypeInfoResolver:
                     break;
 
                 case JsonTypeInfoResolverChain otherChain:
index fc34a37..7072d9e 100644 (file)
@@ -174,6 +174,12 @@ namespace System.Text.Json
         }
 
         [DoesNotReturn]
+        public static void ThrowInvalidOperationException_JsonSerializerIsReflectionDisabled()
+        {
+            throw new InvalidOperationException(SR.JsonSerializerIsReflectionDisabled);
+        }
+
+        [DoesNotReturn]
         public static void ThrowInvalidOperationException_SerializationConverterOnAttributeInvalid(Type classType, MemberInfo? memberInfo)
         {
             string location = classType.ToString();
index d88e2a2..b5a523c 100644 (file)
@@ -213,16 +213,16 @@ namespace System.Text.Json.Serialization.Tests
         public void WriteRootLevelAsyncEnumerableSync_ThrowsNotSupportedException()
         {
             IAsyncEnumerable<int> asyncEnumerable = new MockedAsyncEnumerable<int>(Enumerable.Range(1, 10));
-            Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize(asyncEnumerable));
-            Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize(new MemoryStream(), asyncEnumerable));
+            Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize(asyncEnumerable, Serializer.DefaultOptions));
+            Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize(new MemoryStream(), asyncEnumerable, Serializer.DefaultOptions));
         }
 
         [Fact]
         public void WriteNestedAsyncEnumerableSync_ThrowsNotSupportedException()
         {
             IAsyncEnumerable<int> asyncEnumerable = new MockedAsyncEnumerable<int>(Enumerable.Range(1, 10));
-            Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize(new { Data = asyncEnumerable }));
-            Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize(new MemoryStream(), new { Data = asyncEnumerable }));
+            Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize(new AsyncEnumerableDto<int> { Data = asyncEnumerable }, Serializer.DefaultOptions));
+            Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize(new MemoryStream(), new AsyncEnumerableDto<int> { Data = asyncEnumerable }, Serializer.DefaultOptions));
         }
 
         [Fact]
index 7c4621f..4a11d3c 100644 (file)
@@ -508,9 +508,6 @@ namespace System.Text.Json.Serialization.Tests
 
                 Assert.NotNull(options.TypeInfoResolver);
                 Assert.True(options.TypeInfoResolver is not DefaultJsonTypeInfoResolver);
-                IList<IJsonTypeInfoResolver> resolverList = Assert.IsAssignableFrom<IList<IJsonTypeInfoResolver>>(options.TypeInfoResolver);
-
-                Assert.Empty(resolverList);
                 Assert.Empty(options.TypeInfoResolverChain);
 
                 Assert.Throws<NotSupportedException>(() => options.GetTypeInfo(typeof(string)));
@@ -518,11 +515,19 @@ namespace System.Text.Json.Serialization.Tests
                 Assert.False(options.TryGetTypeInfo(typeof(string), out JsonTypeInfo? typeInfo));
                 Assert.Null(typeInfo);
 
-                Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize("string"));
-                Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize("string", options));
+                InvalidOperationException ex;
+
+                ex = Assert.Throws<InvalidOperationException>(() => JsonSerializer.Serialize("string"));
+                Assert.Contains("JsonSerializerOptions.TypeInfoResolver", ex.Message);
+
+                ex = Assert.Throws<InvalidOperationException>(() => JsonSerializer.Serialize("string", options));
+                Assert.Contains("JsonSerializerOptions.TypeInfoResolver", ex.Message);
+
+                ex = Assert.Throws<InvalidOperationException>(() => JsonSerializer.Deserialize<string>("\"string\""));
+                Assert.Contains("JsonSerializerOptions.TypeInfoResolver", ex.Message);
 
-                Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<string>("\"string\""));
-                Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<string>("\"string\"", options));
+                ex = Assert.Throws<InvalidOperationException>(() => JsonSerializer.Deserialize<string>("\"string\"", options));
+                Assert.Contains("JsonSerializerOptions.TypeInfoResolver", ex.Message);
 
             }, options).Dispose();
         }
@@ -552,8 +557,14 @@ namespace System.Text.Json.Serialization.Tests
                 Assert.Throws<NotSupportedException>(() => options.GetTypeInfo(typeof(string)));
                 Assert.Throws<NotSupportedException>(() => options.GetConverter(typeof(string)));
 
-                Assert.Throws<InvalidOperationException>(() => JsonSerializer.Serialize("string", options));
-                Assert.Throws<InvalidOperationException>(() => JsonSerializer.Deserialize<string>("\"string\"", options));
+                InvalidOperationException ex;
+
+                ex = Assert.Throws<InvalidOperationException>(() => JsonSerializer.Serialize("string", options));
+                Assert.Contains("JsonSerializerOptions.TypeInfoResolver", ex.Message);
+
+                ex = Assert.Throws<InvalidOperationException>(() => JsonSerializer.Deserialize<string>("\"string\"", options));
+                Assert.Contains("JsonSerializerOptions.TypeInfoResolver", ex.Message);
+
                 Assert.False(options.TryGetTypeInfo(typeof(string), out JsonTypeInfo? typeInfo));
                 Assert.Null(typeInfo);
 
index e0db8f3..d126c2c 100644 (file)
@@ -18,18 +18,18 @@ public static class Program
         MyPoco valueToSerialize = new MyPoco { Value = 42 };
 
         // The default resolver should not surface DefaultJsonTypeInfoResolver.
-        if (JsonSerializerOptions.Default.TypeInfoResolver is not IList<IJsonTypeInfoResolver> { Count: 0 })
+        if (JsonSerializerOptions.Default.TypeInfoResolver is DefaultJsonTypeInfoResolver)
         {
             return -1;
         }
 
-        // Serializing with options unset should throw NotSupportedException.
+        // Serializing with options unset should throw InvalidOperationException.
         try
         {
             JsonSerializer.Serialize(valueToSerialize);
             return -2;
         }
-        catch (NotSupportedException)
+        catch (InvalidOperationException)
         {
         }