Skip to content

Commit

Permalink
add nullable type reflection cache
Browse files Browse the repository at this point in the history
  • Loading branch information
israellot committed Aug 12, 2022
1 parent cb9eed9 commit c6554b3
Show file tree
Hide file tree
Showing 9 changed files with 173 additions and 29 deletions.
Original file line number Diff line number Diff line change
@@ -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<NullablePropertiesTestClass> DeserializeTypeWithNullables()
{
return JsonConvert.DeserializeObject<List<NullablePropertiesTestClass>>(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
4 changes: 1 addition & 3 deletions Src/Newtonsoft.Json/Converters/BinaryConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
8 changes: 2 additions & 6 deletions Src/Newtonsoft.Json/Converters/KeyValuePairConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -144,9 +142,7 @@ public override void WriteJson(JsonWriter writer, object? value, JsonSerializer
/// </returns>
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())
{
Expand Down
7 changes: 3 additions & 4 deletions Src/Newtonsoft.Json/Converters/StringEnumConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -266,11 +266,10 @@ public override void WriteJson(JsonWriter writer, object? value, JsonSerializer
/// </returns>
public override bool CanConvert(Type objectType)
{
Type t = (ReflectionUtils.IsNullableType(objectType))
? Nullable.GetUnderlyingType(objectType)!
: objectType;
Type t = ReflectionUtils.GetUnderlyingTypeIfNullable(objectType);

return t.IsEnum();
}

}
}
2 changes: 1 addition & 1 deletion Src/Newtonsoft.Json/Linq/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ public static IJEnumerable<JToken> Children<T>(this IEnumerable<T> 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);
Expand Down
5 changes: 1 addition & 4 deletions Src/Newtonsoft.Json/Schema/JsonSchemaGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion Src/Newtonsoft.Json/Serialization/JsonContract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
78 changes: 71 additions & 7 deletions Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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);
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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<Type, NullableTypeReflectionInfo> NullableInfoCache = new ConcurrentDictionary<Type, NullableTypeReflectionInfo>();
#else
private readonly static Dictionary<Type, NullableTypeReflectionInfo> NullableInfoCache = new Dictionary<Type, NullableTypeReflectionInfo>();
#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

}
}

}

0 comments on commit c6554b3

Please sign in to comment.