From 5d363fa3af421d728ebd7f09a565e7e5a7a00887 Mon Sep 17 00:00:00 2001 From: Petr Onderka Date: Sun, 13 Oct 2019 09:38:34 +0200 Subject: [PATCH 1/5] Cache polymorphic properties --- .../JsonClassInfo.AddProperty.cs | 31 ++++++++++++------- .../Json/Serialization/JsonPropertyInfo.cs | 3 ++ .../JsonSerializer.Read.HandleArray.cs | 2 +- .../JsonSerializer.Read.HandleValue.cs | 2 +- .../JsonSerializer.Write.Helpers.cs | 2 +- .../tests/Serialization/Object.WriteTests.cs | 7 +++++ 6 files changed, 33 insertions(+), 14 deletions(-) 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 66fa35363f87..1e28a8f2341c 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,10 +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.Generic; +using System.Collections.Concurrent; using System.Diagnostics; using System.Reflection; using System.Text.Json.Serialization; +using System.Threading; namespace System.Text.Json { @@ -209,18 +210,26 @@ internal JsonPropertyInfo CreateRootObject(JsonSerializerOptions options) options: options); } - internal JsonPropertyInfo CreatePolymorphicProperty(JsonPropertyInfo property, Type runtimePropertyType, JsonSerializerOptions options) + internal JsonPropertyInfo GetOrAddPolymorphicProperty(JsonPropertyInfo property, Type runtimePropertyType, JsonSerializerOptions options) { - JsonPropertyInfo runtimeProperty = CreateProperty( - property.DeclaredPropertyType, runtimePropertyType, - property.ImplementedPropertyType, - property.PropertyInfo, - parentClassType: Type, - converter: null, - options: options); - property.CopyRuntimeSettingsTo(runtimeProperty); + static JsonPropertyInfo CreateRuntimeProperty(Type runtimePropertyType, (JsonPropertyInfo property, JsonSerializerOptions options, Type classType) arg) + { + JsonPropertyInfo runtimeProperty = CreateProperty( + arg.property.DeclaredPropertyType, runtimePropertyType, + arg.property.ImplementedPropertyType, + arg.property.PropertyInfo, + parentClassType: arg.classType, + converter: null, + options: arg.options); + + arg.property.CopyRuntimeSettingsTo(runtimeProperty); + + return runtimeProperty; + } + + var cache = LazyInitializer.EnsureInitialized(ref property.RuntimePropertyCache, () => new ConcurrentDictionary()); - return runtimeProperty; + return cache.GetOrAdd(runtimePropertyType, (type, arg) => CreateRuntimeProperty(type, arg), (property, options, Type)); } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs index 9604b4ab5e03..3fc708936990 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.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.Diagnostics; using System.Reflection; using System.Text.Json.Serialization; @@ -584,5 +585,7 @@ private void VerifyWrite(int originalDepth, Utf8JsonWriter writer) ThrowHelper.ThrowJsonException_SerializationConverterWrite(ConverterBase); } } + + public ConcurrentDictionary RuntimePropertyCache; } } 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 2d56b20edb7e..b90ba41a5f08 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 @@ -31,7 +31,7 @@ private static void HandleStartArray( } 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..21e1086ab8cb 100644 --- a/src/System.Text.Json/tests/Serialization/Object.WriteTests.cs +++ b/src/System.Text.Json/tests/Serialization/Object.WriteTests.cs @@ -75,5 +75,12 @@ public int this[int index] public string NonIndexerProp { get; set; } } + + [Fact] + public static void WriteCollectionAsObject() + { + string json = JsonSerializer.Serialize(new { Prop = (object)new[] { 0 } }); + Assert.Equal(@"{""Prop"":[0]}", json); + } } } From 92b9b1e6f8e29e02ec8aed8b4d3169249a0ecf58 Mon Sep 17 00:00:00 2001 From: Petr Onderka Date: Thu, 17 Oct 2019 21:49:36 +0200 Subject: [PATCH 2/5] Move RuntimePropertyCache to JsonClassInfo --- .../Serialization/JsonClassInfo.AddProperty.cs | 15 ++++++++------- .../Text/Json/Serialization/JsonClassInfo.cs | 4 ++++ .../Text/Json/Serialization/JsonPropertyInfo.cs | 3 --- 3 files changed, 12 insertions(+), 10 deletions(-) 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 1e28a8f2341c..5d8d1a41079e 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 @@ -212,24 +212,25 @@ internal JsonPropertyInfo CreateRootObject(JsonSerializerOptions options) internal JsonPropertyInfo GetOrAddPolymorphicProperty(JsonPropertyInfo property, Type runtimePropertyType, JsonSerializerOptions options) { - static JsonPropertyInfo CreateRuntimeProperty(Type runtimePropertyType, (JsonPropertyInfo property, JsonSerializerOptions options, Type classType) arg) + static JsonPropertyInfo CreateRuntimeProperty((JsonPropertyInfo property, Type runtimePropertyType) key, (JsonSerializerOptions options, Type classType) arg) { JsonPropertyInfo runtimeProperty = CreateProperty( - arg.property.DeclaredPropertyType, runtimePropertyType, - arg.property.ImplementedPropertyType, - arg.property.PropertyInfo, + key.property.DeclaredPropertyType, key.runtimePropertyType, + key.property.ImplementedPropertyType, + key.property.PropertyInfo, parentClassType: arg.classType, converter: null, options: arg.options); - arg.property.CopyRuntimeSettingsTo(runtimeProperty); + key.property.CopyRuntimeSettingsTo(runtimeProperty); return runtimeProperty; } - var cache = LazyInitializer.EnsureInitialized(ref property.RuntimePropertyCache, () => new ConcurrentDictionary()); + ConcurrentDictionary<(JsonPropertyInfo, Type), JsonPropertyInfo> cache = + LazyInitializer.EnsureInitialized(ref RuntimePropertyCache, () => new ConcurrentDictionary<(JsonPropertyInfo, Type), JsonPropertyInfo>()); - return cache.GetOrAdd(runtimePropertyType, (type, arg) => CreateRuntimeProperty(type, arg), (property, options, Type)); + return cache.GetOrAdd((property, runtimePropertyType), (key, arg) => CreateRuntimeProperty(key, arg), (options, Type)); } } } 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 aefe60a5aecc..f70a69c2bccc 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 proerties, 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/JsonPropertyInfo.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs index 3fc708936990..9604b4ab5e03 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System.Collections; -using System.Collections.Concurrent; using System.Diagnostics; using System.Reflection; using System.Text.Json.Serialization; @@ -585,7 +584,5 @@ private void VerifyWrite(int originalDepth, Utf8JsonWriter writer) ThrowHelper.ThrowJsonException_SerializationConverterWrite(ConverterBase); } } - - public ConcurrentDictionary RuntimePropertyCache; } } From 2f0cf1706d68c50b7da688e5ab96d4c4c332d28f Mon Sep 17 00:00:00 2001 From: Petr Onderka Date: Thu, 17 Oct 2019 21:57:56 +0200 Subject: [PATCH 3/5] Added test of RuntimePropertyCache using properties with different attributes --- .../tests/Serialization/Object.WriteTests.cs | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/System.Text.Json/tests/Serialization/Object.WriteTests.cs b/src/System.Text.Json/tests/Serialization/Object.WriteTests.cs index 21e1086ab8cb..5089cc24abf0 100644 --- a/src/System.Text.Json/tests/Serialization/Object.WriteTests.cs +++ b/src/System.Text.Json/tests/Serialization/Object.WriteTests.cs @@ -77,10 +77,28 @@ public int this[int index] } [Fact] - public static void WriteCollectionAsObject() + 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 => ""; + } } } From 826bc615be42b473bc45083cc364926ee96e1081 Mon Sep 17 00:00:00 2001 From: Petr Onderka Date: Thu, 17 Oct 2019 22:10:20 +0200 Subject: [PATCH 4/5] Fixed typo Co-Authored-By: Ahson Khan --- .../src/System/Text/Json/Serialization/JsonClassInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 f70a69c2bccc..27c8aa8fba6f 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 @@ -28,7 +28,7 @@ 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 proerties, keyed on property and runtime type. + // 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. From df451817b8f51d262f236d2d0bf6cf3e012c96ff Mon Sep 17 00:00:00 2001 From: Petr Onderka Date: Fri, 18 Oct 2019 12:40:42 +0200 Subject: [PATCH 5/5] Use allocating overload of GetOrAdd on .Net Standard 2.0 --- .../Text/Json/Serialization/JsonClassInfo.AddProperty.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 5d8d1a41079e..8eadefaa5d07 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 @@ -229,8 +229,11 @@ static JsonPropertyInfo CreateRuntimeProperty((JsonPropertyInfo property, Type r 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 } } }