From 9e2581be74585605b4f49e0110d65c85c17cb323 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 10 Apr 2014 00:24:21 +1200 Subject: [PATCH] -Improved cast operator deserialize performance --- .../Converters/IsoDateTimeConverterTests.cs | 2 + .../Properties/AssemblyInfo.cs | 2 +- .../Serialization/JsonSerializerTest.cs | 49 +++++ .../Properties/AssemblyInfo.cs | 2 +- Src/Newtonsoft.Json/Utilities/ConvertUtils.cs | 173 ++++++++++++------ 5 files changed, 172 insertions(+), 56 deletions(-) diff --git a/Src/Newtonsoft.Json.Tests/Converters/IsoDateTimeConverterTests.cs b/Src/Newtonsoft.Json.Tests/Converters/IsoDateTimeConverterTests.cs index 937ee426..7ebd1edf 100644 --- a/Src/Newtonsoft.Json.Tests/Converters/IsoDateTimeConverterTests.cs +++ b/Src/Newtonsoft.Json.Tests/Converters/IsoDateTimeConverterTests.cs @@ -275,6 +275,7 @@ namespace Newtonsoft.Json.Tests.Converters Console.WriteLine(jsonText); } +#if !NET20 [Test] public void DeserializeDateTimeOffset() { @@ -292,5 +293,6 @@ namespace Newtonsoft.Json.Tests.Converters Assert.AreEqual(dto, test); Assert.AreEqual(dto.ToString("o"), test.ToString("o")); } +#endif } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs index 91ba79cf..2a1f467f 100644 --- a/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs +++ b/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs @@ -75,4 +75,4 @@ using System.Security; // by using the '*' as shown below: [assembly: AssemblyVersion("6.0.0.0")] -[assembly: AssemblyFileVersion("6.0.1.17131")] +[assembly: AssemblyFileVersion("6.0.2.17209")] diff --git a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs index 88b9af6b..4532eb6f 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs @@ -548,6 +548,55 @@ namespace Newtonsoft.Json.Tests.Serialization Assert.AreEqual(RegexOptions.CultureInvariant, r2.Options); } + [Test] + public void ConversionOperator() + { + // Creating a simple dictionary that has a non-string key + var dictStore = new Dictionary(); + for (var i = 0; i < 800; i++) + { + dictStore.Add(new DictionaryKeyCast(i.ToString(CultureInfo.InvariantCulture), i), i); + } + var settings = new JsonSerializerSettings { Formatting = Formatting.Indented }; + var jsonSerializer = JsonSerializer.Create(settings); + var ms = new MemoryStream(); + + var streamWriter = new StreamWriter(ms); + jsonSerializer.Serialize(streamWriter, dictStore); + streamWriter.Flush(); + + ms.Seek(0, SeekOrigin.Begin); + + var stopWatch = Stopwatch.StartNew(); + var deserialize = jsonSerializer.Deserialize(new StreamReader(ms), typeof(Dictionary)); + stopWatch.Stop(); + + Console.WriteLine("Time elapsed: " + stopWatch.ElapsedMilliseconds); + } + + internal class DictionaryKeyCast + { + private String _name; + private int _number; + + public DictionaryKeyCast(String name, int number) + { + _name = name; + _number = number; + } + + public override string ToString() + { + return _name + " " + _number; + } + + public static implicit operator DictionaryKeyCast(string dictionaryKey) + { + var strings = dictionaryKey.Split(' '); + return new DictionaryKeyCast(strings[0], Convert.ToInt32(strings[1])); + } + } + #if !NET20 [DataContract] public class BaseDataContractWithHidden diff --git a/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs index 62523697..2fe52faf 100644 --- a/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs +++ b/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs @@ -91,5 +91,5 @@ using System.Security; // by using the '*' as shown below: [assembly: AssemblyVersion("6.0.0.0")] -[assembly: AssemblyFileVersion("6.0.1.17203")] +[assembly: AssemblyFileVersion("6.0.2.17209")] [assembly: CLSCompliant(true)] diff --git a/Src/Newtonsoft.Json/Utilities/ConvertUtils.cs b/Src/Newtonsoft.Json/Utilities/ConvertUtils.cs index d87e904c..38d2a86a 100644 --- a/Src/Newtonsoft.Json/Utilities/ConvertUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/ConvertUtils.cs @@ -345,15 +345,51 @@ namespace Newtonsoft.Json.Utilities } #endif - #region Convert - /// - /// Converts the value to the specified type. - /// - /// The value to convert. - /// The culture to use when converting. - /// The type to convert the value to. - /// The converted type. + #region TryConvert + internal enum ConvertResult + { + Success, + CannotConvertNull, + NotInstantiableType, + NoValidConversion + } + public static object Convert(object initialValue, CultureInfo culture, Type targetType) + { + object value; + switch (TryConvertInternal(initialValue, culture, targetType, out value)) + { + case ConvertResult.Success: + return value; + case ConvertResult.CannotConvertNull: + throw new Exception("Can not convert null {0} into non-nullable {1}.".FormatWith(CultureInfo.InvariantCulture, initialValue.GetType(), targetType)); + case ConvertResult.NotInstantiableType: + throw new ArgumentException("Target type {0} is not a value type or a non-abstract class.".FormatWith(CultureInfo.InvariantCulture, targetType), "targetType"); + case ConvertResult.NoValidConversion: + throw new InvalidOperationException("Can not convert from {0} to {1}.".FormatWith(CultureInfo.InvariantCulture, initialValue.GetType(), targetType)); + default: + throw new InvalidOperationException("Unexpected conversion result."); + } + } + + private static bool TryConvert(object initialValue, CultureInfo culture, Type targetType, out object value) + { + try + { + if (TryConvertInternal(initialValue, culture, targetType, out value) == ConvertResult.Success) + return true; + + value = null; + return false; + } + catch + { + value = null; + return false; + } + } + + private static ConvertResult TryConvertInternal(object initialValue, CultureInfo culture, Type targetType, out object value) { if (initialValue == null) throw new ArgumentNullException("initialValue"); @@ -364,7 +400,10 @@ namespace Newtonsoft.Json.Utilities Type initialType = initialValue.GetType(); if (targetType == initialType) - return initialValue; + { + value = initialValue; + return ConvertResult.Success; + } // use Convert.ChangeType if both types are IConvertible if (ConvertUtils.IsConvertible(initialValue.GetType()) && ConvertUtils.IsConvertible(targetType)) @@ -372,41 +411,75 @@ namespace Newtonsoft.Json.Utilities if (targetType.IsEnum()) { if (initialValue is string) - return Enum.Parse(targetType, initialValue.ToString(), true); + { + value = Enum.Parse(targetType, initialValue.ToString(), true); + return ConvertResult.Success; + } else if (IsInteger(initialValue)) - return Enum.ToObject(targetType, initialValue); + { + value = Enum.ToObject(targetType, initialValue); + return ConvertResult.Success; + } } - return System.Convert.ChangeType(initialValue, targetType, culture); + value = System.Convert.ChangeType(initialValue, targetType, culture); + return ConvertResult.Success; } #if !NET20 if (initialValue is DateTime && targetType == typeof(DateTimeOffset)) - return new DateTimeOffset((DateTime)initialValue); + { + value = new DateTimeOffset((DateTime)initialValue); + return ConvertResult.Success; + } #endif if (initialValue is byte[] && targetType == typeof(Guid)) - return new Guid((byte[])initialValue); + { + value = new Guid((byte[])initialValue); + return ConvertResult.Success; + } if (initialValue is string) { if (targetType == typeof(Guid)) - return new Guid((string)initialValue); + { + value = new Guid((string)initialValue); + return ConvertResult.Success; + } if (targetType == typeof(Uri)) - return new Uri((string)initialValue, UriKind.RelativeOrAbsolute); + { + value = new Uri((string)initialValue, UriKind.RelativeOrAbsolute); + return ConvertResult.Success; + } if (targetType == typeof(TimeSpan)) - return ParseTimeSpan((string)initialValue); - if (targetType == typeof (byte[])) - return System.Convert.FromBase64String((string)initialValue); + { + value = ParseTimeSpan((string)initialValue); + return ConvertResult.Success; + } + if (targetType == typeof(byte[])) + { + value = System.Convert.FromBase64String((string)initialValue); + return ConvertResult.Success; + } if (typeof(Type).IsAssignableFrom(targetType)) - return Type.GetType((string)initialValue, true); + { + value = Type.GetType((string)initialValue, true); + return ConvertResult.Success; + } } #if !(NET20 || NET35 || PORTABLE40 || PORTABLE) if (targetType == typeof(BigInteger)) - return ToBigInteger(initialValue); + { + value = ToBigInteger(initialValue); + return ConvertResult.Success; + } if (initialValue is BigInteger) - return FromBigInteger((BigInteger)initialValue, targetType); + { + value = FromBigInteger((BigInteger)initialValue, targetType); + return ConvertResult.Success; + } #endif #if !(NETFX_CORE || PORTABLE40 || PORTABLE) @@ -414,58 +487,50 @@ namespace Newtonsoft.Json.Utilities TypeConverter toConverter = GetConverter(initialType); if (toConverter != null && toConverter.CanConvertTo(targetType)) - return toConverter.ConvertTo(null, culture, initialValue, targetType); + { + value = toConverter.ConvertTo(null, culture, initialValue, targetType); + return ConvertResult.Success; + } TypeConverter fromConverter = GetConverter(targetType); if (fromConverter != null && fromConverter.CanConvertFrom(initialType)) - return fromConverter.ConvertFrom(null, culture, initialValue); + { + value = fromConverter.ConvertFrom(null, culture, initialValue); + return ConvertResult.Success; + } #endif #if !(NETFX_CORE || PORTABLE40 || PORTABLE) // handle DBNull and INullable if (initialValue == DBNull.Value) { if (ReflectionUtils.IsNullable(targetType)) - return EnsureTypeAssignable(null, initialType, targetType); + { + value = EnsureTypeAssignable(null, initialType, targetType); + return ConvertResult.Success; + } - throw new Exception("Can not convert null {0} into non-nullable {1}.".FormatWith(CultureInfo.InvariantCulture, initialType, targetType)); + // cannot convert null to non-nullable + value = null; + return ConvertResult.CannotConvertNull; } #endif #if !(NETFX_CORE || PORTABLE40 || PORTABLE) if (initialValue is INullable) - return EnsureTypeAssignable(ToValue((INullable)initialValue), initialType, targetType); + { + value = EnsureTypeAssignable(ToValue((INullable)initialValue), initialType, targetType); + return ConvertResult.Success; + } #endif if (targetType.IsInterface() || targetType.IsGenericTypeDefinition() || targetType.IsAbstract()) - throw new ArgumentException("Target type {0} is not a value type or a non-abstract class.".FormatWith(CultureInfo.InvariantCulture, targetType), "targetType"); - - throw new InvalidOperationException("Can not convert from {0} to {1}.".FormatWith(CultureInfo.InvariantCulture, initialType, targetType)); - } - #endregion - - #region TryConvert - /// - /// Converts the value to the specified type. - /// - /// The value to convert. - /// The culture to use when converting. - /// The type to convert the value to. - /// The converted value if the conversion was successful or the default value of T if it failed. - /// - /// true if initialValue was converted successfully; otherwise, false. - /// - public static bool TryConvert(object initialValue, CultureInfo culture, Type targetType, out object convertedValue) - { - try { - convertedValue = Convert(initialValue, culture, targetType); - return true; - } - catch - { - convertedValue = null; - return false; + value = null; + return ConvertResult.NotInstantiableType; } + + value = null; + return ConvertResult.NoValidConversion; } #endregion