-DiscriminatedUnionConverter performance improvements
This commit is contained in:
@@ -21,6 +21,7 @@
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<UseVSHostingProcess>true</UseVSHostingProcess>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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<Shape> values = new List<Shape>
|
||||
{
|
||||
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<List<Shape>>(json);
|
||||
|
||||
Stopwatch ts = new Stopwatch();
|
||||
ts.Start();
|
||||
|
||||
for (int i = 0; i < 2000; i++)
|
||||
{
|
||||
JsonConvert.DeserializeObject<List<Shape>>(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<UnionCase> Cases;
|
||||
public Converter<object, int> TagReader { get; set; }
|
||||
}
|
||||
|
||||
public class UnionCase
|
||||
{
|
||||
public int Tag;
|
||||
public string Name;
|
||||
public PropertyInfo[] Fields;
|
||||
public Converter<object, object[]> FieldReader;
|
||||
public Converter<object[], object> Constructor;
|
||||
}
|
||||
|
||||
private Union CreateUnion(Type t)
|
||||
{
|
||||
Union u = new Union();
|
||||
|
||||
u.TagReader = FSharpFunc<object, int>.ToConverter(FSharpValue.PreComputeUnionTagReader(t, null));
|
||||
u.Cases = new List<UnionCase>();
|
||||
|
||||
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<object, object[]>.ToConverter(FSharpValue.PreComputeUnionReader(unionCaseInfo, null));
|
||||
unionCase.Constructor = FSharpFunc<object[], object>.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()
|
||||
{
|
||||
|
||||
@@ -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
|
||||
/// </summary>
|
||||
public class DiscriminatedUnionConverter : JsonConverter
|
||||
{
|
||||
#region UnionDefinition
|
||||
internal class Union
|
||||
{
|
||||
public List<UnionCase> Cases;
|
||||
public Converter<object, int> TagReader { get; set; }
|
||||
}
|
||||
|
||||
internal class UnionCase
|
||||
{
|
||||
public int Tag;
|
||||
public string Name;
|
||||
public PropertyInfo[] Fields;
|
||||
public Converter<object, object[]> FieldReader;
|
||||
public Converter<object[], object> Constructor;
|
||||
}
|
||||
#endregion
|
||||
|
||||
private const string CasePropertyName = "Case";
|
||||
private const string FieldsPropertyName = "Fields";
|
||||
|
||||
private static readonly ThreadSafeStore<Type, Union> UnionCache = new ThreadSafeStore<Type, Union>(CreateUnion);
|
||||
private static readonly ThreadSafeStore<Type, Type> UnionTypeLookupCache = new ThreadSafeStore<Type, Type>(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<object, int>)FSharpUtils.PreComputeUnionTagReader(null, t, null);
|
||||
u.Cases = new List<UnionCase>();
|
||||
|
||||
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<object, object[]>)FSharpUtils.PreComputeUnionReader(null, unionCaseInfo, null);
|
||||
unionCase.Constructor = (Converter<object[], object>)FSharpUtils.PreComputeUnionConstructor(null, unionCaseInfo, null);
|
||||
|
||||
u.Cases.Add(unionCase);
|
||||
}
|
||||
|
||||
return u;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the JSON representation of the object.
|
||||
/// </summary>
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -44,12 +44,13 @@ namespace Newtonsoft.Json.Utilities
|
||||
|
||||
public static Assembly FSharpCoreAssembly { get; private set; }
|
||||
public static MethodCall<object, object> IsUnion { get; private set; }
|
||||
public static MethodCall<object, object> GetUnionFields { get; private set; }
|
||||
public static MethodCall<object, object> GetUnionCases { get; private set; }
|
||||
public static MethodCall<object, object> MakeUnion { get; private set; }
|
||||
public static MethodCall<object, object> PreComputeUnionTagReader { get; private set; }
|
||||
public static MethodCall<object, object> PreComputeUnionReader { get; private set; }
|
||||
public static MethodCall<object, object> PreComputeUnionConstructor { get; private set; }
|
||||
public static Func<object, object> GetUnionCaseInfoDeclaringType { get; private set; }
|
||||
public static Func<object, object> GetUnionCaseInfoName { get; private set; }
|
||||
public static Func<object, object> GetUnionCaseInfo { get; private set; }
|
||||
public static Func<object, object> GetUnionCaseFields { get; private set; }
|
||||
public static Func<object, object> GetUnionCaseInfoTag { get; private set; }
|
||||
public static MethodCall<object, object> 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<object>(getUnionFieldsMethodInfo);
|
||||
|
||||
GetUnionCaseInfo = JsonTypeReflector.ReflectionDelegateFactory.CreateGet<object>(getUnionFieldsMethodInfo.ReturnType.GetProperty("Item1"));
|
||||
GetUnionCaseFields = JsonTypeReflector.ReflectionDelegateFactory.CreateGet<object>(getUnionFieldsMethodInfo.ReturnType.GetProperty("Item2"));
|
||||
|
||||
MethodInfo makeUnionMethodInfo = fsharpValue.GetMethod("MakeUnion", BindingFlags.Public | BindingFlags.Static);
|
||||
MakeUnion = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall<object>(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<object>(unionCaseInfo.GetProperty("Name"));
|
||||
GetUnionCaseInfoTag = JsonTypeReflector.ReflectionDelegateFactory.CreateGet<object>(unionCaseInfo.GetProperty("Tag"));
|
||||
GetUnionCaseInfoDeclaringType = JsonTypeReflector.ReflectionDelegateFactory.CreateGet<object>(unionCaseInfo.GetProperty("DeclaringType"));
|
||||
GetUnionCaseInfoFields = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall<object>(unionCaseInfo.GetMethod("GetFields"));
|
||||
|
||||
Type listModule = fsharpCoreAssembly.GetType("Microsoft.FSharp.Collections.ListModule");
|
||||
@@ -104,6 +102,24 @@ namespace Newtonsoft.Json.Utilities
|
||||
}
|
||||
}
|
||||
|
||||
private static MethodCall<object, object> 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<object, object> inner = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall<object>(innerMethodInfo);
|
||||
MethodCall<object, object> toConvert = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall<object>(toConvertMethodInfo);
|
||||
|
||||
MethodCall<object, object> methodCall = (target, args) =>
|
||||
{
|
||||
object result = inner(target, args);
|
||||
|
||||
return toConvert(null, result);
|
||||
};
|
||||
|
||||
return methodCall;
|
||||
}
|
||||
|
||||
public static ObjectConstructor<object> CreateSeq(Type t)
|
||||
{
|
||||
MethodInfo seqType = _ofSeq.MakeGenericMethod(t);
|
||||
|
||||
Reference in New Issue
Block a user