diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs index 4ba60eb069d7..4bcb3cbff0fe 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs @@ -2,9 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections.Concurrent; using System.Diagnostics; using System.Reflection; using System.Text.Json.Serialization; +using System.Threading; namespace System.Text.Json { @@ -130,33 +132,44 @@ internal JsonPropertyInfo CreateRootObject(JsonSerializerOptions options) options); } - internal JsonPropertyInfo CreatePolymorphicProperty(JsonPropertyInfo property, Type runtimePropertyType, JsonSerializerOptions options) + internal JsonPropertyInfo GetOrAddPolymorphicProperty(JsonPropertyInfo property, Type runtimePropertyType, JsonSerializerOptions options) { - ClassType classType = GetClassType( - runtimePropertyType, - Type, - property.PropertyInfo, - out _, - out Type elementType, - out Type nullableType, - out _, - out JsonConverter converter, - checkForAddMethod: false, - options); - - JsonPropertyInfo runtimeProperty = CreateProperty( - property.DeclaredPropertyType, - runtimePropertyType, - property.PropertyInfo, - parentClassType: Type, - collectionElementType: elementType, - nullableType, - converter, - classType, - options: options); - property.CopyRuntimeSettingsTo(runtimeProperty); + static JsonPropertyInfo CreateRuntimeProperty((JsonPropertyInfo property, Type runtimePropertyType) key, (JsonSerializerOptions options, Type classType) arg) + { + ClassType classType = GetClassType( + key.runtimePropertyType, + arg.classType, + key.property.PropertyInfo, + out _, + out Type elementType, + out Type nullableType, + out _, + out JsonConverter converter, + checkForAddMethod: false, + arg.options); + + JsonPropertyInfo runtimeProperty = CreateProperty( + key.property.DeclaredPropertyType, + key.runtimePropertyType, + key.property.PropertyInfo, + parentClassType: arg.classType, + collectionElementType: elementType, + nullableType, + converter, + classType, + options: arg.options); + key.property.CopyRuntimeSettingsTo(runtimeProperty); + + return runtimeProperty; + } - return runtimeProperty; + ConcurrentDictionary<(JsonPropertyInfo, Type), JsonPropertyInfo> cache = + LazyInitializer.EnsureInitialized(ref RuntimePropertyCache, () => new ConcurrentDictionary<(JsonPropertyInfo, Type), JsonPropertyInfo>()); +#if BUILDING_INBOX_LIBRARY + return cache.GetOrAdd((property, runtimePropertyType), (key, arg) => CreateRuntimeProperty(key, arg), (options, Type)); +#else + return cache.GetOrAdd((property, runtimePropertyType), key => CreateRuntimeProperty(key, (options, Type))); +#endif } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs index b10ed84b71d6..d34f8a19d39e 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; @@ -27,6 +28,9 @@ internal sealed partial class JsonClassInfo // All of the serializable properties on a POCO (except the optional extension property) keyed on property name. public volatile Dictionary PropertyCache; + // Serializable runtime/polymorphic properties, keyed on property and runtime type. + public ConcurrentDictionary<(JsonPropertyInfo, Type), JsonPropertyInfo> RuntimePropertyCache; + // All of the serializable properties on a POCO including the optional extension property. // Used for performance during serialization instead of 'PropertyCache' above. public volatile JsonPropertyInfo[] PropertyCacheArray; diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs index 073a73f88280..b03b93ae18e2 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs @@ -29,7 +29,7 @@ private static void HandleStartArray(JsonSerializerOptions options, ref ReadStac } else if (state.Current.JsonClassInfo.ClassType == ClassType.Unknown) { - jsonPropertyInfo = state.Current.JsonClassInfo.CreatePolymorphicProperty(jsonPropertyInfo, typeof(object), options); + jsonPropertyInfo = state.Current.JsonClassInfo.GetOrAddPolymorphicProperty(jsonPropertyInfo, typeof(object), options); } // Verify that we have a valid enumerable. diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleValue.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleValue.cs index fb15f4e23662..45620aa94309 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleValue.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleValue.cs @@ -24,7 +24,7 @@ private static void HandleValue(JsonTokenType tokenType, JsonSerializerOptions o } else if (state.Current.JsonClassInfo.ClassType == ClassType.Unknown) { - jsonPropertyInfo = state.Current.JsonClassInfo.CreatePolymorphicProperty(jsonPropertyInfo, typeof(object), options); + jsonPropertyInfo = state.Current.JsonClassInfo.GetOrAddPolymorphicProperty(jsonPropertyInfo, typeof(object), options); } jsonPropertyInfo.Read(tokenType, ref state, ref reader); diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Helpers.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Helpers.cs index 88acff65b2cb..b990c376a867 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Helpers.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Helpers.cs @@ -31,7 +31,7 @@ private static void GetRuntimePropertyInfo(object value, JsonClassInfo jsonClass // Nothing to do for typeof(object) if (runtimeType != typeof(object)) { - jsonPropertyInfo = jsonClassInfo.CreatePolymorphicProperty(jsonPropertyInfo, runtimeType, options); + jsonPropertyInfo = jsonClassInfo.GetOrAddPolymorphicProperty(jsonPropertyInfo, runtimeType, options); } } } diff --git a/src/System.Text.Json/tests/Serialization/Object.WriteTests.cs b/src/System.Text.Json/tests/Serialization/Object.WriteTests.cs index 52a960efcd4d..5089cc24abf0 100644 --- a/src/System.Text.Json/tests/Serialization/Object.WriteTests.cs +++ b/src/System.Text.Json/tests/Serialization/Object.WriteTests.cs @@ -75,5 +75,30 @@ public int this[int index] public string NonIndexerProp { get; set; } } + + [Fact] + public static void WritePolymorhicSimple() + { + string json = JsonSerializer.Serialize(new { Prop = (object)new[] { 0 } }); + Assert.Equal(@"{""Prop"":[0]}", json); + } + + [Fact] + public static void WritePolymorphicDifferentAttributes() + { + string json = JsonSerializer.Serialize(new Polymorphic()); + Assert.Equal(@"{""P1"":"""",""p_3"":""""}", json); + } + + private class Polymorphic + { + public object P1 => ""; + + [JsonIgnore] + public object P2 => ""; + + [JsonPropertyName("p_3")] + public object P3 => ""; + } } }