diff --git a/Src/Newtonsoft.Json.TestConsole/Newtonsoft.Json.TestConsole.csproj b/Src/Newtonsoft.Json.TestConsole/Newtonsoft.Json.TestConsole.csproj index b5f3f346..e88712fa 100644 --- a/Src/Newtonsoft.Json.TestConsole/Newtonsoft.Json.TestConsole.csproj +++ b/Src/Newtonsoft.Json.TestConsole/Newtonsoft.Json.TestConsole.csproj @@ -21,6 +21,7 @@ DEBUG;TRACE prompt 4 + true AnyCPU diff --git a/Src/Newtonsoft.Json.TestConsole/Program.cs b/Src/Newtonsoft.Json.TestConsole/Program.cs index 2e43b374..305b1e56 100644 --- a/Src/Newtonsoft.Json.TestConsole/Program.cs +++ b/Src/Newtonsoft.Json.TestConsole/Program.cs @@ -32,6 +32,7 @@ using System.Text; using System.Threading.Tasks; using System.Web.Script.Serialization; using Newtonsoft.Json.Tests; +using Newtonsoft.Json.Tests.Converters; namespace Newtonsoft.Json.TestConsole { @@ -53,8 +54,10 @@ namespace Newtonsoft.Json.TestConsole //DeserializeLargeJson(); //WriteLargeJson(); //DeserializeJson(); - ReadLargeJson(); + //ReadLargeJson(); //ReadLargeJsonJavaScriptSerializer(); + DiscriminatedUnionConverterTests t = new DiscriminatedUnionConverterTests(); + t.SerializePerformance(); Console.WriteLine(); Console.WriteLine("Finished"); diff --git a/Src/Newtonsoft.Json.Tests/Converters/DiscriminatedUnionConverterTests.cs b/Src/Newtonsoft.Json.Tests/Converters/DiscriminatedUnionConverterTests.cs index 42824b09..05b1e081 100644 --- a/Src/Newtonsoft.Json.Tests/Converters/DiscriminatedUnionConverterTests.cs +++ b/Src/Newtonsoft.Json.Tests/Converters/DiscriminatedUnionConverterTests.cs @@ -23,6 +23,10 @@ // OTHER DEALINGS IN THE SOFTWARE. #endregion +using System.Diagnostics; +using System.Reflection; +using Microsoft.FSharp.Core; +using Microsoft.FSharp.Reflection; #if !(NET35 || NET20 || NETFX_CORE || ASPNETCORE50) using System; using System.Collections.Generic; @@ -83,6 +87,58 @@ namespace Newtonsoft.Json.Tests.Converters Assert.AreEqual(@"{""Case"":""AUD""}", json); } + [Test] + public void SerializePerformance() + { + List values = new List + { + Shape.NewRectangle(10.0, 5.0), + Shape.NewCircle(7.5) + }; + + string json = JsonConvert.SerializeObject(values, Formatting.Indented); + + Stopwatch ts = new Stopwatch(); + ts.Start(); + + for (int i = 0; i < 2000; i++) + { + JsonConvert.SerializeObject(values); + } + + ts.Stop(); + + Console.WriteLine(ts.Elapsed.TotalSeconds); + Console.WriteLine(json); + } + + [Test] + public void DeserializePerformance() + { + string json = @"[ + {""Case"":""Rectangle"",""Fields"":[10.0,5.0]}, + {""Case"":""Rectangle"",""Fields"":[10.0,5.0]}, + {""Case"":""Rectangle"",""Fields"":[10.0,5.0]}, + {""Case"":""Rectangle"",""Fields"":[10.0,5.0]}, + {""Case"":""Rectangle"",""Fields"":[10.0,5.0]} +]"; + + JsonConvert.DeserializeObject>(json); + + Stopwatch ts = new Stopwatch(); + ts.Start(); + + for (int i = 0; i < 2000; i++) + { + JsonConvert.DeserializeObject>(json); + } + + ts.Stop(); + + Console.WriteLine(ts.Elapsed.TotalSeconds); + Console.WriteLine(json); + } + [Test] public void SerializeUnionWithFields() { @@ -116,6 +172,82 @@ namespace Newtonsoft.Json.Tests.Converters Assert.AreEqual(10.0, r.width); } + public class Union + { + public List Cases; + public Converter TagReader { get; set; } + } + + public class UnionCase + { + public int Tag; + public string Name; + public PropertyInfo[] Fields; + public Converter FieldReader; + public Converter Constructor; + } + + private Union CreateUnion(Type t) + { + Union u = new Union(); + + u.TagReader = FSharpFunc.ToConverter(FSharpValue.PreComputeUnionTagReader(t, null)); + u.Cases = new List(); + + UnionCaseInfo[] cases = FSharpType.GetUnionCases(t, null); + + foreach (UnionCaseInfo unionCaseInfo in cases) + { + UnionCase unionCase = new UnionCase(); + unionCase.Tag = unionCaseInfo.Tag; + unionCase.Name = unionCaseInfo.Name; + unionCase.Fields = unionCaseInfo.GetFields(); + unionCase.FieldReader = FSharpFunc.ToConverter(FSharpValue.PreComputeUnionReader(unionCaseInfo, null)); + unionCase.Constructor = FSharpFunc.ToConverter(FSharpValue.PreComputeUnionConstructor(unionCaseInfo, null)); + + u.Cases.Add(unionCase); + } + + return u; + } + + [Test] + public void Serialize() + { + Shape value = Shape.NewRectangle(10.0, 5.0); + + Union union = CreateUnion(value.GetType()); + + int tag = union.TagReader.Invoke(value); + + UnionCase caseInfo = union.Cases.Single(c => c.Tag == tag); + + object[] fields = caseInfo.FieldReader.Invoke(value); + + foreach (object field in fields) + { + Console.WriteLine(field); + } + } + + [Test] + public void Deserialize() + { + Union union = CreateUnion(typeof(Shape.Rectangle)); + + UnionCase caseInfo = union.Cases.Single(c => c.Name == "Rectangle"); + + Shape.Rectangle value = (Shape.Rectangle)caseInfo.Constructor.Invoke(new object[] + { + 10.0, 5.0 + }); + + Console.WriteLine(value.ToString()); + + Console.WriteLine(value.width); + Console.WriteLine(value.length); + } + [Test] public void DeserializeBasicUnion_NoMatch() { diff --git a/Src/Newtonsoft.Json/Converters/DiscriminatedUnionConverter.cs b/Src/Newtonsoft.Json/Converters/DiscriminatedUnionConverter.cs index 0edb0f92..01639085 100644 --- a/Src/Newtonsoft.Json/Converters/DiscriminatedUnionConverter.cs +++ b/Src/Newtonsoft.Json/Converters/DiscriminatedUnionConverter.cs @@ -28,7 +28,6 @@ using Newtonsoft.Json.Linq; using System; using System.Collections; using System.Collections.Generic; -using System.ComponentModel; #if NET20 using Newtonsoft.Json.Utilities.LinqBridge; #else @@ -46,9 +45,67 @@ namespace Newtonsoft.Json.Converters /// public class DiscriminatedUnionConverter : JsonConverter { + #region UnionDefinition + internal class Union + { + public List Cases; + public Converter TagReader { get; set; } + } + + internal class UnionCase + { + public int Tag; + public string Name; + public PropertyInfo[] Fields; + public Converter FieldReader; + public Converter Constructor; + } + #endregion + private const string CasePropertyName = "Case"; private const string FieldsPropertyName = "Fields"; + private static readonly ThreadSafeStore UnionCache = new ThreadSafeStore(CreateUnion); + private static readonly ThreadSafeStore UnionTypeLookupCache = new ThreadSafeStore(CreateUnionTypeLookup); + + private static Type CreateUnionTypeLookup(Type t) + { + // this lookup is because cases with fields are derived from union type + // need to get declaring type to avoid duplicate Unions in cache + + // hacky but I can't find an API to get the declaring type without GetUnionCases + object[] cases = (object[])FSharpUtils.GetUnionCases(null, t, null); + + object caseInfo = cases.First(); + + Type unionType = (Type)FSharpUtils.GetUnionCaseInfoDeclaringType(caseInfo); + return unionType; + } + + private static Union CreateUnion(Type t) + { + Union u = new Union(); + + u.TagReader = (Converter)FSharpUtils.PreComputeUnionTagReader(null, t, null); + u.Cases = new List(); + + object[] cases = (object[])FSharpUtils.GetUnionCases(null, t, null); + + foreach (object unionCaseInfo in cases) + { + UnionCase unionCase = new UnionCase(); + unionCase.Tag = (int)FSharpUtils.GetUnionCaseInfoTag(unionCaseInfo); + unionCase.Name = (string)FSharpUtils.GetUnionCaseInfoName(unionCaseInfo); + unionCase.Fields = (PropertyInfo[])FSharpUtils.GetUnionCaseInfoFields(unionCaseInfo); + unionCase.FieldReader = (Converter)FSharpUtils.PreComputeUnionReader(null, unionCaseInfo, null); + unionCase.Constructor = (Converter)FSharpUtils.PreComputeUnionConstructor(null, unionCaseInfo, null); + + u.Cases.Add(unionCase); + } + + return u; + } + /// /// Writes the JSON representation of the object. /// @@ -59,21 +116,21 @@ namespace Newtonsoft.Json.Converters { DefaultContractResolver resolver = serializer.ContractResolver as DefaultContractResolver; - Type t = value.GetType(); + Type unionType = UnionTypeLookupCache.Get(value.GetType()); + Union union = UnionCache.Get(unionType); - object result = FSharpUtils.GetUnionFields(null, value, t, null); - object info = FSharpUtils.GetUnionCaseInfo(result); - object fields = FSharpUtils.GetUnionCaseFields(result); - object caseName = FSharpUtils.GetUnionCaseInfoName(info); - object[] fieldsAsArray = fields as object[]; + int tag = union.TagReader(value); + UnionCase caseInfo = union.Cases.Single(c => c.Tag == tag); writer.WriteStartObject(); writer.WritePropertyName((resolver != null) ? resolver.GetResolvedPropertyName(CasePropertyName) : CasePropertyName); - writer.WriteValue((string)caseName); - if (fieldsAsArray != null && fieldsAsArray.Length > 0) + writer.WriteValue(caseInfo.Name); + if (caseInfo.Fields != null && caseInfo.Fields.Length > 0) { + object[] fields = caseInfo.FieldReader(value); + writer.WritePropertyName((resolver != null) ? resolver.GetResolvedPropertyName(FieldsPropertyName) : FieldsPropertyName); - serializer.Serialize(writer, fields); + serializer.Serialize(writer, fields); } writer.WriteEndObject(); } @@ -91,7 +148,7 @@ namespace Newtonsoft.Json.Converters if (reader.TokenType == JsonToken.Null) return null; - object matchingCaseInfo = null; + UnionCase caseInfo = null; string caseName = null; JArray fields = null; @@ -105,20 +162,13 @@ namespace Newtonsoft.Json.Converters { ReadAndAssert(reader); - IEnumerable cases = (IEnumerable)FSharpUtils.GetUnionCases(null, objectType, null); + Union union = UnionCache.Get(objectType); caseName = reader.Value.ToString(); - foreach (object c in cases) - { - if ((string)FSharpUtils.GetUnionCaseInfoName(c) == caseName) - { - matchingCaseInfo = c; - break; - } - } + caseInfo = union.Cases.SingleOrDefault(c => c.Name == caseName); - if (matchingCaseInfo == null) + if (caseInfo == null) throw JsonSerializationException.Create(reader, "No union type found with the name '{0}'.".FormatWith(CultureInfo.InvariantCulture, caseName)); } else if (string.Equals(propertyName, FieldsPropertyName, StringComparison.OrdinalIgnoreCase)) @@ -137,31 +187,30 @@ namespace Newtonsoft.Json.Converters ReadAndAssert(reader); } - if (matchingCaseInfo == null) + if (caseInfo == null) throw JsonSerializationException.Create(reader, "No '{0}' property with union name found.".FormatWith(CultureInfo.InvariantCulture, CasePropertyName)); - - PropertyInfo[] fieldProperties = (PropertyInfo[])FSharpUtils.GetUnionCaseInfoFields(matchingCaseInfo); - object[] typedFieldValues = new object[fieldProperties.Length]; - if (fieldProperties.Length > 0 && fields == null) + object[] typedFieldValues = new object[caseInfo.Fields.Length]; + + if (caseInfo.Fields.Length > 0 && fields == null) throw JsonSerializationException.Create(reader, "No '{0}' property with union fields found.".FormatWith(CultureInfo.InvariantCulture, FieldsPropertyName)); if (fields != null) { - if (fieldProperties.Length != fields.Count) + if (caseInfo.Fields.Length != fields.Count) throw JsonSerializationException.Create(reader, "The number of field values does not match the number of properties definied by union '{0}'.".FormatWith(CultureInfo.InvariantCulture, caseName)); for (int i = 0; i < fields.Count; i++) { JToken t = fields[i]; - PropertyInfo fieldProperty = fieldProperties[i]; + PropertyInfo fieldProperty = caseInfo.Fields[i]; typedFieldValues[i] = t.ToObject(fieldProperty.PropertyType, serializer); } } - return FSharpUtils.MakeUnion(null, matchingCaseInfo, typedFieldValues, null); + return caseInfo.Constructor(typedFieldValues); } /// diff --git a/Src/Newtonsoft.Json/Utilities/FSharpUtils.cs b/Src/Newtonsoft.Json/Utilities/FSharpUtils.cs index 0cedbdb3..fdcb7dc8 100644 --- a/Src/Newtonsoft.Json/Utilities/FSharpUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/FSharpUtils.cs @@ -44,12 +44,13 @@ namespace Newtonsoft.Json.Utilities public static Assembly FSharpCoreAssembly { get; private set; } public static MethodCall IsUnion { get; private set; } - public static MethodCall GetUnionFields { get; private set; } public static MethodCall GetUnionCases { get; private set; } - public static MethodCall MakeUnion { get; private set; } + public static MethodCall PreComputeUnionTagReader { get; private set; } + public static MethodCall PreComputeUnionReader { get; private set; } + public static MethodCall PreComputeUnionConstructor { get; private set; } + public static Func GetUnionCaseInfoDeclaringType { get; private set; } public static Func GetUnionCaseInfoName { get; private set; } - public static Func GetUnionCaseInfo { get; private set; } - public static Func GetUnionCaseFields { get; private set; } + public static Func GetUnionCaseInfoTag { get; private set; } public static MethodCall GetUnionCaseInfoFields { get; private set; } public const string FSharpSetTypeName = "FSharpSet`1"; @@ -76,18 +77,15 @@ namespace Newtonsoft.Json.Utilities Type fsharpValue = fsharpCoreAssembly.GetType("Microsoft.FSharp.Reflection.FSharpValue"); - MethodInfo getUnionFieldsMethodInfo = fsharpValue.GetMethod("GetUnionFields", BindingFlags.Public | BindingFlags.Static); - GetUnionFields = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall(getUnionFieldsMethodInfo); - - GetUnionCaseInfo = JsonTypeReflector.ReflectionDelegateFactory.CreateGet(getUnionFieldsMethodInfo.ReturnType.GetProperty("Item1")); - GetUnionCaseFields = JsonTypeReflector.ReflectionDelegateFactory.CreateGet(getUnionFieldsMethodInfo.ReturnType.GetProperty("Item2")); - - MethodInfo makeUnionMethodInfo = fsharpValue.GetMethod("MakeUnion", BindingFlags.Public | BindingFlags.Static); - MakeUnion = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall(makeUnionMethodInfo); + PreComputeUnionTagReader = CreateFSharpFuncCall(fsharpValue, "PreComputeUnionTagReader"); + PreComputeUnionReader = CreateFSharpFuncCall(fsharpValue, "PreComputeUnionReader"); + PreComputeUnionConstructor = CreateFSharpFuncCall(fsharpValue, "PreComputeUnionConstructor"); Type unionCaseInfo = fsharpCoreAssembly.GetType("Microsoft.FSharp.Reflection.UnionCaseInfo"); GetUnionCaseInfoName = JsonTypeReflector.ReflectionDelegateFactory.CreateGet(unionCaseInfo.GetProperty("Name")); + GetUnionCaseInfoTag = JsonTypeReflector.ReflectionDelegateFactory.CreateGet(unionCaseInfo.GetProperty("Tag")); + GetUnionCaseInfoDeclaringType = JsonTypeReflector.ReflectionDelegateFactory.CreateGet(unionCaseInfo.GetProperty("DeclaringType")); GetUnionCaseInfoFields = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall(unionCaseInfo.GetMethod("GetFields")); Type listModule = fsharpCoreAssembly.GetType("Microsoft.FSharp.Collections.ListModule"); @@ -104,6 +102,24 @@ namespace Newtonsoft.Json.Utilities } } + private static MethodCall CreateFSharpFuncCall(Type type, string methodName) + { + MethodInfo innerMethodInfo = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static); + MethodInfo toConvertMethodInfo = innerMethodInfo.ReturnType.GetMethod("ToConverter", BindingFlags.Public | BindingFlags.Static); + + MethodCall inner = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall(innerMethodInfo); + MethodCall toConvert = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall(toConvertMethodInfo); + + MethodCall methodCall = (target, args) => + { + object result = inner(target, args); + + return toConvert(null, result); + }; + + return methodCall; + } + public static ObjectConstructor CreateSeq(Type t) { MethodInfo seqType = _ofSeq.MakeGenericMethod(t);