Permalink
Browse files

-DiscriminatedUnionConverter performance improvements

  • Loading branch information...
JamesNK committed Mar 7, 2015
1 parent b1184ef commit 95e04ba96e1c1ce40bcaab5c93544f37f9abf2e2
@@ -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.Threading.Tasks;
using System.Web.Script.Serialization;
using Newtonsoft.Json.Tests;
using Newtonsoft.Json.Tests.Converters;
namespace Newtonsoft.Json.TestConsole
{
@@ -53,8 +54,10 @@ public static void Main(string[] args)
//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 @@ public void SerializeBasicUnion()
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 @@ public void DeserializeUnionWithFields()
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;

This comment has been minimized.

Show comment
Hide comment
@SimonCropp

SimonCropp Mar 19, 2015

this doesn exist in portable. perhaps a custom delegate instead?

@SimonCropp

SimonCropp Mar 19, 2015

this doesn exist in portable. perhaps a custom delegate instead?

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 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 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s
{
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 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
if (reader.TokenType == JsonToken.Null)
return null;
object matchingCaseInfo = null;
UnionCase caseInfo = null;
string caseName = null;
JArray fields = null;
@@ -105,20 +162,13 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
{
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 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
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>
Oops, something went wrong.

0 comments on commit 95e04ba

Please sign in to comment.