diff --git a/Src/Newtonsoft.Json.Tests/Benchmarks/DeserializeNullableBenchmarks.cs b/Src/Newtonsoft.Json.Tests/Benchmarks/DeserializeNullableBenchmarks.cs new file mode 100644 index 000000000..97c468951 --- /dev/null +++ b/Src/Newtonsoft.Json.Tests/Benchmarks/DeserializeNullableBenchmarks.cs @@ -0,0 +1,92 @@ +#region License +// Copyright (c) 2007 James Newton-King +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +#endregion + +#if HAVE_BENCHMARKS + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml; +using BenchmarkDotNet.Attributes; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Tests.TestObjects; +using Newtonsoft.Json.Converters; +using BenchmarkDotNet.Attributes.Jobs; + +namespace Newtonsoft.Json.Tests.Benchmarks +{ + + public class DeserializeNullableBenchmarks + { + private static readonly string JsonText; + private static readonly JsonSerializerSettings SerializerSettings; + + + static DeserializeNullableBenchmarks() + { + var sb = new StringBuilder(); + sb.Append('['); + for (var i = 0; i <= 1000; i++) + { + sb.Append("{\"a\":1,\"b\":\"c\",\"c\":true},"); + } + sb.Append(']'); + + JsonText = sb.ToString(); + + + SerializerSettings = new JsonSerializerSettings(); + SerializerSettings.Converters.Add(new StringEnumConverter()); + } + + + [Benchmark] + public List DeserializeTypeWithNullables() + { + return JsonConvert.DeserializeObject>(JsonText, SerializerSettings); + } + + public class NullablePropertiesTestClass + { + public int? a { get; set; } + + public TestEnum? b { get; set; } + + public bool? c { get; set; } + + public enum TestEnum + { + a, + b, + c + } + } + } +} + +#endif \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Converters/BinaryConverter.cs b/Src/Newtonsoft.Json/Converters/BinaryConverter.cs index 47833a2ac..e89fdb4d8 100644 --- a/Src/Newtonsoft.Json/Converters/BinaryConverter.cs +++ b/Src/Newtonsoft.Json/Converters/BinaryConverter.cs @@ -134,9 +134,7 @@ private static void EnsureReflectionObject(Type t) throw JsonSerializationException.Create(reader, "Unexpected token parsing binary. Expected String or StartArray, got {0}.".FormatWith(CultureInfo.InvariantCulture, reader.TokenType)); } - Type t = (ReflectionUtils.IsNullableType(objectType)) - ? Nullable.GetUnderlyingType(objectType)! - : objectType; + Type t = ReflectionUtils.GetUnderlyingTypeIfNullable(objectType); #if HAVE_LINQ if (t.FullName == BinaryTypeName) diff --git a/Src/Newtonsoft.Json/Converters/JavaScriptDateTimeConverter.cs b/Src/Newtonsoft.Json/Converters/JavaScriptDateTimeConverter.cs index ff1231f25..c07d70ba1 100644 --- a/Src/Newtonsoft.Json/Converters/JavaScriptDateTimeConverter.cs +++ b/Src/Newtonsoft.Json/Converters/JavaScriptDateTimeConverter.cs @@ -97,9 +97,7 @@ public override void WriteJson(JsonWriter writer, object? value, JsonSerializer } #if HAVE_DATE_TIME_OFFSET - Type t = (ReflectionUtils.IsNullableType(objectType)) - ? Nullable.GetUnderlyingType(objectType)! - : objectType; + Type t = ReflectionUtils.GetUnderlyingTypeIfNullable(objectType); if (t == typeof(DateTimeOffset)) { return new DateTimeOffset(d); diff --git a/Src/Newtonsoft.Json/Converters/KeyValuePairConverter.cs b/Src/Newtonsoft.Json/Converters/KeyValuePairConverter.cs index 0221e9337..ca460ed2e 100644 --- a/Src/Newtonsoft.Json/Converters/KeyValuePairConverter.cs +++ b/Src/Newtonsoft.Json/Converters/KeyValuePairConverter.cs @@ -101,9 +101,7 @@ public override void WriteJson(JsonWriter writer, object? value, JsonSerializer reader.ReadAndAssert(); - Type t = ReflectionUtils.IsNullableType(objectType) - ? Nullable.GetUnderlyingType(objectType)! - : objectType; + Type t = ReflectionUtils.GetUnderlyingTypeIfNullable(objectType); ReflectionObject reflectionObject = ReflectionObjectPerType.Get(t); JsonContract keyContract = serializer.ContractResolver.ResolveContract(reflectionObject.GetType(KeyName)); @@ -144,9 +142,7 @@ public override void WriteJson(JsonWriter writer, object? value, JsonSerializer /// public override bool CanConvert(Type objectType) { - Type t = (ReflectionUtils.IsNullableType(objectType)) - ? Nullable.GetUnderlyingType(objectType)! - : objectType; + Type t = ReflectionUtils.GetUnderlyingTypeIfNullable(objectType); if (t.IsValueType() && t.IsGenericType()) { diff --git a/Src/Newtonsoft.Json/Converters/StringEnumConverter.cs b/Src/Newtonsoft.Json/Converters/StringEnumConverter.cs index a98400808..0f7537711 100644 --- a/Src/Newtonsoft.Json/Converters/StringEnumConverter.cs +++ b/Src/Newtonsoft.Json/Converters/StringEnumConverter.cs @@ -222,7 +222,7 @@ public override void WriteJson(JsonWriter writer, object? value, JsonSerializer } bool isNullable = ReflectionUtils.IsNullableType(objectType); - Type t = isNullable ? Nullable.GetUnderlyingType(objectType)! : objectType; + Type t = ReflectionUtils.GetUnderlyingTypeIfNullable(objectType); try { @@ -266,11 +266,10 @@ public override void WriteJson(JsonWriter writer, object? value, JsonSerializer /// public override bool CanConvert(Type objectType) { - Type t = (ReflectionUtils.IsNullableType(objectType)) - ? Nullable.GetUnderlyingType(objectType)! - : objectType; + Type t = ReflectionUtils.GetUnderlyingTypeIfNullable(objectType); return t.IsEnum(); } + } } diff --git a/Src/Newtonsoft.Json/Linq/Extensions.cs b/Src/Newtonsoft.Json/Linq/Extensions.cs index cd333a2f7..8547492ed 100644 --- a/Src/Newtonsoft.Json/Linq/Extensions.cs +++ b/Src/Newtonsoft.Json/Linq/Extensions.cs @@ -291,7 +291,7 @@ public static IJEnumerable Children(this IEnumerable source) where #pragma warning restore CS8653 // A default expression introduces a null value for a type parameter. } - targetType = Nullable.GetUnderlyingType(targetType)!; + targetType = ReflectionUtils.GetUnderlyingTypeIfNullable(targetType); } return (U?)System.Convert.ChangeType(value.Value, targetType, CultureInfo.InvariantCulture); diff --git a/Src/Newtonsoft.Json/Schema/JsonSchemaGenerator.cs b/Src/Newtonsoft.Json/Schema/JsonSchemaGenerator.cs index 7cdab6111..f3d9e4a4b 100644 --- a/Src/Newtonsoft.Json/Schema/JsonSchemaGenerator.cs +++ b/Src/Newtonsoft.Json/Schema/JsonSchemaGenerator.cs @@ -447,10 +447,7 @@ private JsonSchemaType GetJsonSchemaType(Type type, Required valueRequired) if (valueRequired != Required.Always && ReflectionUtils.IsNullable(type)) { schemaType = JsonSchemaType.Null; - if (ReflectionUtils.IsNullableType(type)) - { - type = Nullable.GetUnderlyingType(type); - } + type = ReflectionUtils.GetUnderlyingTypeIfNullable(type); } PrimitiveTypeCode typeCode = ConvertUtils.GetTypeCode(type); diff --git a/Src/Newtonsoft.Json/Serialization/JsonContract.cs b/Src/Newtonsoft.Json/Serialization/JsonContract.cs index d4f802cb9..f6b31f5be 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonContract.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonContract.cs @@ -248,7 +248,7 @@ internal JsonContract(Type underlyingType) IsNullable = ReflectionUtils.IsNullable(underlyingType); - NonNullableUnderlyingType = (IsNullable && ReflectionUtils.IsNullableType(underlyingType)) ? Nullable.GetUnderlyingType(underlyingType)! : underlyingType; + NonNullableUnderlyingType = ReflectionUtils.GetUnderlyingTypeIfNullable(underlyingType); _createdType = CreatedType = NonNullableUnderlyingType; diff --git a/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs b/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs index f3d594244..30b605849 100644 --- a/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs @@ -24,16 +24,22 @@ #endregion using System; + using System.Collections.Generic; #if HAVE_BIG_INTEGER using System.Numerics; #endif using System.Reflection; using System.Collections; +#if HAVE_CONCURRENT_COLLECTIONS +using System.Collections.Concurrent; +#endif using System.Globalization; using System.Text; using System.Runtime.CompilerServices; using System.Diagnostics.CodeAnalysis; +using System.Threading; + #if !HAVE_LINQ using Newtonsoft.Json.Utilities.LinqBridge; #else @@ -275,16 +281,17 @@ public static bool IsNullable(Type t) public static bool IsNullableType(Type t) { - ValidationUtils.ArgumentNotNull(t, nameof(t)); + return ReflectionUtilsCache.GetTypeInfo(t).IsNullable; + } - return (t.IsGenericType() && t.GetGenericTypeDefinition() == typeof(Nullable<>)); + public static Type GetUnderlyingTypeIfNullable(Type t) + { + return ReflectionUtilsCache.GetTypeInfo(t).UnderlyingType; } public static Type EnsureNotNullableType(Type t) { - return (IsNullableType(t)) - ? Nullable.GetUnderlyingType(t)! - : t; + return ReflectionUtilsCache.GetTypeInfo(t).UnderlyingType; } public static Type EnsureNotByRefType(Type t) @@ -301,7 +308,7 @@ public static bool IsGenericDefinition(Type type, Type genericInterfaceDefinitio return false; } - Type t = type.GetGenericTypeDefinition(); + Type t = ReflectionUtilsCache.GetTypeInfo(type).GenericTypeDefinition!; return (t == genericInterfaceDefinition); } @@ -375,7 +382,7 @@ private static bool InheritsGenericDefinitionInternal(Type type, Type genericCla Type? currentType = type; do { - if (currentType.IsGenericType() && genericClassDefinition == currentType.GetGenericTypeDefinition()) + if (currentType.IsGenericType() && genericClassDefinition == ReflectionUtilsCache.GetTypeInfo(type).GenericTypeDefinition) { implementingType = currentType; return true; @@ -1109,4 +1116,61 @@ public static bool IsMethodOverridden(Type currentType, Type methodDeclaringType return Activator.CreateInstance(type); } } + + internal static class ReflectionUtilsCache + { + public readonly struct NullableTypeReflectionInfo + { + public readonly bool IsNullable; + + public readonly Type UnderlyingType; + + public readonly Type? GenericTypeDefinition; + + public NullableTypeReflectionInfo( + bool isNullable, + Type underlyingType, + Type? genericTypeDefinition) + { + IsNullable = isNullable; + UnderlyingType = underlyingType; + GenericTypeDefinition = genericTypeDefinition; + } + } + +#if HAVE_CONCURRENT_COLLECTIONS + private readonly static ConcurrentDictionary NullableInfoCache = new ConcurrentDictionary(); +#else + private readonly static Dictionary NullableInfoCache = new Dictionary(); +#endif + + public static NullableTypeReflectionInfo GetTypeInfo(Type t) + { + ValidationUtils.ArgumentNotNull(t, nameof(t)); + + if (!t.IsGenericType() || !t.IsValueType()) + return new NullableTypeReflectionInfo(false, t, null); + +#if !HAVE_CONCURRENT_DICTIONARY + lock(NullableInfoCache){ +#endif + if (!NullableInfoCache.TryGetValue(t, out var info)) + { + var genericTypeDefinition = t.GetGenericTypeDefinition(); + var isNullable = (genericTypeDefinition == typeof(Nullable<>)); + var underlyingType = Nullable.GetUnderlyingType(t) ?? t; + info = new NullableTypeReflectionInfo(isNullable, underlyingType, genericTypeDefinition); + + NullableInfoCache[t] = info; + } + + return info; + +#if !HAVE_CONCURRENT_DICTIONARY + } +#endif + + } + } + }