Ensure metadata originating from converters is surfaced while JsonTypeInfo is mutable...
authorEirik Tsarpalis <eirik.tsarpalis@gmail.com>
Wed, 20 Jul 2022 11:49:13 +0000 (14:49 +0300)
committerGitHub <noreply@github.com>
Wed, 20 Jul 2022 11:49:13 +0000 (12:49 +0100)
src/libraries/System.Text.Json/src/Resources/Strings.resx
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonObjectConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandlePropertyName.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionJsonTypeInfoOfT.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/SourceGenJsonTypeInfoOfT.cs
src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs
src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/DefaultJsonTypeInfoResolverTests.JsonTypeInfo.cs

index ef169c7..f4f1cc9 100644 (file)
     <value>The JSON property name for '{0}.{1}' cannot be null.</value>
   </data>
   <data name="SerializationDataExtensionPropertyInvalid" xml:space="preserve">
-    <value>The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary&lt;string, JsonElement&gt;' or 'IDictionary&lt;string, object&gt;', or be 'JsonObject'.</value>
+    <value>The extension data property '{0}.{1}' is invalid. It must implement 'IDictionary&lt;string, JsonElement&gt;' or 'IDictionary&lt;string, object&gt;', or be 'JsonObject'.</value>
   </data>
   <data name="SerializationDuplicateTypeAttribute" xml:space="preserve">
     <value>The type '{0}' cannot have more than one member that has the attribute '{1}'.</value>
index eeb62a1..b3279a0 100644 (file)
@@ -3,14 +3,15 @@
 
 using System.Diagnostics;
 using System.Text.Json.Nodes;
+using System.Text.Json.Serialization.Metadata;
 
 namespace System.Text.Json.Serialization.Converters
 {
     internal sealed class JsonObjectConverter : JsonConverter<JsonObject>
     {
-        internal override object CreateObject(JsonSerializerOptions options)
+        internal override void ConfigureJsonTypeInfo(JsonTypeInfo jsonTypeInfo, JsonSerializerOptions options)
         {
-            return new JsonObject(options.GetNodeOptions());
+            jsonTypeInfo.CreateObjectForExtensionDataProperty = () => new JsonObject(options.GetNodeOptions());
         }
 
         internal override void ReadElementAndSetProperty(
@@ -27,7 +28,7 @@ namespace System.Text.Json.Serialization.Converters
             JsonObject jObject = (JsonObject)obj;
 
             Debug.Assert(value == null || value is JsonNode);
-            JsonNode? jNodeValue = (JsonNode?)value;
+            JsonNode? jNodeValue = value;
 
             jObject[propertyName] = jNodeValue;
         }
index 4fa80af..2f6945c 100644 (file)
@@ -82,7 +82,7 @@ namespace System.Text.Json.Serialization.Converters
                         {
                             Debug.Assert(jsonPropertyInfo == state.Current.JsonTypeInfo.ExtensionDataProperty);
                             state.Current.JsonPropertyNameAsString = dataExtKey;
-                            JsonSerializer.CreateDataExtensionProperty(obj, jsonPropertyInfo, options);
+                            JsonSerializer.CreateExtensionDataProperty(obj, jsonPropertyInfo, options);
                         }
 
                         ReadPropertyValue(obj, ref state, ref tempReader, jsonPropertyInfo, useExtensionProperty);
@@ -190,7 +190,7 @@ namespace System.Text.Json.Serialization.Converters
                         {
                             Debug.Assert(jsonPropertyInfo == state.Current.JsonTypeInfo.ExtensionDataProperty);
 
-                            JsonSerializer.CreateDataExtensionProperty(obj, jsonPropertyInfo, options);
+                            JsonSerializer.CreateExtensionDataProperty(obj, jsonPropertyInfo, options);
                             object extDictionary = jsonPropertyInfo.GetValueAsObject(obj)!;
 
                             if (extDictionary is IDictionary<string, JsonElement> dict)
index e8ff50c..9045ecb 100644 (file)
@@ -49,14 +49,6 @@ namespace System.Text.Json.Serialization
         /// <summary>
         /// Used to support JsonObject as an extension property in a loosely-typed, trimmable manner.
         /// </summary>
-        internal virtual object CreateObject(JsonSerializerOptions options)
-        {
-            throw new InvalidOperationException(SR.NodeJsonObjectCustomConverterNotAllowedOnExtensionProperty);
-        }
-
-        /// <summary>
-        /// Used to support JsonObject as an extension property in a loosely-typed, trimmable manner.
-        /// </summary>
         internal virtual void ReadElementAndSetProperty(
             object obj,
             string propertyName,
@@ -64,7 +56,9 @@ namespace System.Text.Json.Serialization
             JsonSerializerOptions options,
             ref ReadStack state)
         {
-            throw new InvalidOperationException(SR.NodeJsonObjectCustomConverterNotAllowedOnExtensionProperty);
+            Debug.Fail("Should not be reachable.");
+
+            throw new InvalidOperationException();
         }
 
         internal abstract JsonParameterInfo CreateJsonParameterInfo();
index 8d13413..598d715 100644 (file)
@@ -56,7 +56,7 @@ namespace System.Text.Json
                     if (createExtensionProperty)
                     {
                         Debug.Assert(obj != null, "obj is null");
-                        CreateDataExtensionProperty(obj, dataExtProperty, options);
+                        CreateExtensionDataProperty(obj, dataExtProperty, options);
                     }
 
                     jsonPropertyInfo = dataExtProperty;
@@ -98,7 +98,7 @@ namespace System.Text.Json
             return unescapedPropertyName;
         }
 
-        internal static void CreateDataExtensionProperty(
+        internal static void CreateExtensionDataProperty(
             object obj,
             JsonPropertyInfo jsonPropertyInfo,
             JsonSerializerOptions options)
@@ -130,18 +130,15 @@ namespace System.Text.Json
                     // Avoid a reference to the JsonNode type for trimming
                     if (jsonPropertyInfo.PropertyType.FullName == JsonTypeInfo.JsonObjectTypeName)
                     {
-                        extensionData = jsonPropertyInfo.EffectiveConverter.CreateObject(options);
+                        ThrowHelper.ThrowInvalidOperationException_NodeJsonObjectCustomConverterNotAllowedOnExtensionProperty();
                     }
                     else
                     {
                         ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(jsonPropertyInfo.PropertyType);
                     }
                 }
-                else
-                {
-                    extensionData = createObjectForExtensionDataProp();
-                }
 
+                extensionData = createObjectForExtensionDataProp();
                 jsonPropertyInfo.SetExtensionDictionaryAsObject(obj, extensionData);
             }
 
index 59fe6c8..3926e4a 100644 (file)
@@ -60,7 +60,7 @@ namespace System.Text.Json.Serialization.Metadata
         private protected abstract void SetCreateObject(Delegate? createObject);
         private protected Func<object>? _createObject;
 
-        internal Func<object>? CreateObjectForExtensionDataProperty { get; private protected set; }
+        internal Func<object>? CreateObjectForExtensionDataProperty { get; set; }
 
         /// <summary>
         /// Gets or sets a callback to be invoked before serialization occurs.
@@ -523,7 +523,7 @@ namespace System.Text.Json.Serialization.Metadata
             }
         }
 
-        internal virtual void Configure()
+        internal void Configure()
         {
             Debug.Assert(Monitor.IsEntered(_configureLock), "Configure called directly, use EnsureConfigured which locks this method");
 
@@ -539,8 +539,6 @@ namespace System.Text.Json.Serialization.Metadata
             Debug.Assert(PropertyInfoForTypeInfo.ConverterStrategy == Converter.ConverterStrategy,
                 $"ConverterStrategy from PropertyInfoForTypeInfo.ConverterStrategy ({PropertyInfoForTypeInfo.ConverterStrategy}) does not match converter's ({Converter.ConverterStrategy})");
 
-            converter.ConfigureJsonTypeInfo(this, Options);
-
             if (Kind == JsonTypeInfoKind.Object)
             {
                 InitializePropertyCache();
index 3397577..89fe3ea 100644 (file)
@@ -35,16 +35,10 @@ namespace System.Text.Json.Serialization.Metadata
             }
 
             CreateObjectForExtensionDataProperty = createObject;
-        }
 
-        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
-            Justification = "The ctor is marked as RequiresUnreferencedCode")]
-        [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode",
-            Justification = "The ctor is marked RequiresDynamicCode.")]
-        internal override void Configure()
-        {
-            base.Configure();
-            Converter.ConfigureJsonTypeInfoUsingReflection(this, Options);
+            // Plug in any converter configuration -- should be run last.
+            converter.ConfigureJsonTypeInfo(this, options);
+            converter.ConfigureJsonTypeInfoUsingReflection(this, options);
         }
 
         [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
index c50b4d0..0d3ec62 100644 (file)
@@ -22,12 +22,15 @@ namespace System.Text.Json.Serialization.Metadata
         {
             PopulatePolymorphismMetadata();
             MapInterfaceTypesToCallbacks();
+
+            // Plug in any converter configuration -- should be run last.
+            converter.ConfigureJsonTypeInfo(this, options);
         }
 
         /// <summary>
         /// Creates serialization metadata for an object.
         /// </summary>
-        public SourceGenJsonTypeInfo(JsonSerializerOptions options, JsonObjectInfoValues<T> objectInfo) : this(GetConverter(objectInfo), options)
+        public SourceGenJsonTypeInfo(JsonSerializerOptions options, JsonObjectInfoValues<T> objectInfo) : base(GetConverter(objectInfo), options)
         {
             if (objectInfo.ObjectWithParameterizedConstructorCreator != null)
             {
@@ -43,6 +46,11 @@ namespace System.Text.Json.Serialization.Metadata
             PropInitFunc = objectInfo.PropertyMetadataInitializer;
             SerializeHandler = objectInfo.SerializeHandler;
             NumberHandling = objectInfo.NumberHandling;
+            PopulatePolymorphismMetadata();
+            MapInterfaceTypesToCallbacks();
+
+            // Plug in any converter configuration -- should be run last.
+            Converter.ConfigureJsonTypeInfo(this, options);
         }
 
         /// <summary>
@@ -54,7 +62,7 @@ namespace System.Text.Json.Serialization.Metadata
             Func<JsonConverter<T>> converterCreator,
             object? createObjectWithArgs = null,
             object? addFunc = null)
-            : this(new JsonMetadataServicesConverter<T>(converterCreator()), options)
+            : base(new JsonMetadataServicesConverter<T>(converterCreator()), options)
         {
             if (collectionInfo is null)
             {
@@ -69,6 +77,11 @@ namespace System.Text.Json.Serialization.Metadata
             CreateObjectWithArgs = createObjectWithArgs;
             AddMethodDelegate = addFunc;
             CreateObject = collectionInfo.ObjectCreator;
+            PopulatePolymorphismMetadata();
+            MapInterfaceTypesToCallbacks();
+
+            // Plug in any converter configuration -- should be run last.
+            Converter.ConfigureJsonTypeInfo(this, options);
         }
 
         private static JsonConverter GetConverter(JsonObjectInfoValues<T> objectInfo)
index 576328b..dee7b47 100644 (file)
@@ -411,6 +411,12 @@ namespace System.Text.Json
         }
 
         [DoesNotReturn]
+        public static void ThrowInvalidOperationException_NodeJsonObjectCustomConverterNotAllowedOnExtensionProperty()
+        {
+            throw new InvalidOperationException(SR.NodeJsonObjectCustomConverterNotAllowedOnExtensionProperty);
+        }
+
+        [DoesNotReturn]
         public static void ThrowNotSupportedException(ref ReadStack state, in Utf8JsonReader reader, NotSupportedException ex)
         {
             string message = ex.Message;
index 61aa1c2..9160010 100644 (file)
@@ -1,6 +1,7 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+using System.Collections;
 using System.Collections.Generic;
 using System.Linq;
 using System.Reflection;
@@ -942,6 +943,24 @@ namespace System.Text.Json.Serialization.Tests
             Assert.True(deserialized.E);
         }
 
+
+        [Theory]
+        [InlineData(typeof(ICollection<string>))]
+        [InlineData(typeof(IList))]
+        [InlineData(typeof(IList<bool>))]
+        [InlineData(typeof(IDictionary))]
+        [InlineData(typeof(IDictionary<string, bool>))]
+        [InlineData(typeof(ISet<Guid>))]
+        public static void AbstractCollectionMetadata_SurfacesCreateObjectWhereApplicable(Type type)
+        {
+            var options = new JsonSerializerOptions();
+            var resolver = new DefaultJsonTypeInfoResolver();
+
+            JsonTypeInfo metadata = resolver.GetTypeInfo(type, options);
+            Assert.NotNull(metadata.CreateObject);
+            Assert.IsAssignableFrom(type, metadata.CreateObject());
+        }
+
         private class ClassWithLargeParameterizedConstructor
         {
             public int A { get; set; }