Skip to content
This repository was archived by the owner on Dec 24, 2022. It is now read-only.

Commit deecbc0

Browse files
committed
Refactor ToObjectDictionary to cache delegates and add support for FromObjectDictionary
1 parent 9142303 commit deecbc0

File tree

4 files changed

+201
-12
lines changed

4 files changed

+201
-12
lines changed

src/ServiceStack.Text/ReflectionExtensions.cs

Lines changed: 131 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
//
1212

1313
using System;
14+
using System.Collections.Concurrent;
1415
using System.Collections.Generic;
1516
using System.ComponentModel;
1617
using System.Linq;
@@ -706,10 +707,11 @@ public static PropertyInfo[] OnlySerializableProperties(this PropertyInfo[] prop
706707
// else return those properties that are not decorated with IgnoreDataMember
707708
return readableProperties
708709
.Where(prop => prop.AllAttributes()
709-
.All(attr => {
710-
var name = attr.GetType().Name;
711-
return !IgnoreAttributesNamed.Contains(name);
712-
}))
710+
.All(attr =>
711+
{
712+
var name = attr.GetType().Name;
713+
return !IgnoreAttributesNamed.Contains(name);
714+
}))
713715
.Where(prop => !JsConfig.ExcludeTypes.Contains(prop.PropertyType))
714716
.ToArray();
715717
}
@@ -1223,7 +1225,7 @@ public static List<Attribute> GetAttributes(this PropertyInfo propertyInfo, Type
12231225
List<Attribute> propertyAttrs;
12241226
return !propertyAttributesMap.TryGetValue(propertyInfo.UniqueKey(), out propertyAttrs)
12251227
? new List<Attribute>()
1226-
: propertyAttrs.Where(x => attrType.IsInstanceOf(x.GetType()) ).ToList();
1228+
: propertyAttrs.Where(x => attrType.IsInstanceOf(x.GetType())).ToList();
12271229
}
12281230

12291231
public static object[] AllAttributes(this PropertyInfo propertyInfo)
@@ -1766,27 +1768,144 @@ public static Type GetCollectionType(this Type type)
17661768
return type.ElementType() ?? type.GetTypeGenericArguments().FirstOrDefault();
17671769
}
17681770

1769-
public static Dictionary<string, object> ToObjectDictionary<T>(this T obj)
1771+
private static readonly ConcurrentDictionary<Type, ObjectDictionaryDefinition> toObjectMapCache =
1772+
new ConcurrentDictionary<Type, ObjectDictionaryDefinition>();
1773+
1774+
internal class ObjectDictionaryDefinition
1775+
{
1776+
public Type Type;
1777+
public readonly List<ObjectDictionaryFieldDefinition> Fields = new List<ObjectDictionaryFieldDefinition>();
1778+
public readonly Dictionary<string, ObjectDictionaryFieldDefinition> FieldsMap = new Dictionary<string, ObjectDictionaryFieldDefinition>();
1779+
1780+
public void Add(string name, ObjectDictionaryFieldDefinition fieldDef)
1781+
{
1782+
Fields.Add(fieldDef);
1783+
FieldsMap[name] = fieldDef;
1784+
}
1785+
}
1786+
1787+
internal class ObjectDictionaryFieldDefinition
1788+
{
1789+
public string Name;
1790+
public Type Type;
1791+
1792+
public PropertyGetterDelegate GetValueFn;
1793+
public PropertySetterDelegate SetValueFn;
1794+
1795+
public Type ConvertType;
1796+
public PropertyGetterDelegate ConvertValueFn;
1797+
1798+
public void SetValue(object instance, object value)
1799+
{
1800+
if (SetValueFn == null)
1801+
return;
1802+
1803+
if (!Type.IsInstanceOfType(value))
1804+
{
1805+
lock (this)
1806+
{
1807+
//Only caches object converter used on first use
1808+
if (ConvertType == null)
1809+
{
1810+
ConvertType = value.GetType();
1811+
ConvertValueFn = TypeConverter.CreateTypeConverter(ConvertType, Type);
1812+
}
1813+
}
1814+
1815+
if (ConvertType.IsInstanceOfType(value))
1816+
{
1817+
value = ConvertValueFn(value);
1818+
}
1819+
else
1820+
{
1821+
var tempConvertFn = TypeConverter.CreateTypeConverter(value.GetType(), Type);
1822+
value = tempConvertFn(value);
1823+
}
1824+
}
1825+
1826+
SetValueFn(instance, value);
1827+
}
1828+
}
1829+
1830+
public static Dictionary<string, object> ToObjectDictionary(this object obj)
17701831
{
1832+
if (obj == null)
1833+
return null;
1834+
17711835
var alreadyDict = obj as Dictionary<string, object>;
17721836
if (alreadyDict != null)
17731837
return alreadyDict;
17741838

1839+
var type = obj.GetType();
1840+
1841+
ObjectDictionaryDefinition def;
1842+
if (!toObjectMapCache.TryGetValue(type, out def))
1843+
toObjectMapCache[type] = def = CreateObjectDictionaryDefinition(type);
1844+
17751845
var dict = new Dictionary<string, object>();
1776-
1777-
foreach (var pi in obj.GetType().GetSerializableProperties())
1846+
1847+
foreach (var fieldDef in def.Fields)
17781848
{
1779-
dict[pi.Name] = pi.GetValue(obj, null);
1849+
dict[fieldDef.Name] = fieldDef.GetValueFn(obj);
1850+
}
1851+
1852+
return dict;
1853+
}
1854+
1855+
public static object FromObjectDictionary(this Dictionary<string, object> values, Type type)
1856+
{
1857+
var alreadyDict = type == typeof(Dictionary<string, object>);
1858+
if (alreadyDict)
1859+
return alreadyDict;
1860+
1861+
ObjectDictionaryDefinition def;
1862+
if (!toObjectMapCache.TryGetValue(type, out def))
1863+
toObjectMapCache[type] = def = CreateObjectDictionaryDefinition(type);
1864+
1865+
var to = type.CreateInstance();
1866+
foreach (var entry in values)
1867+
{
1868+
ObjectDictionaryFieldDefinition fieldDef;
1869+
if (!def.FieldsMap.TryGetValue(entry.Key, out fieldDef) || entry.Value == null)
1870+
continue;
1871+
1872+
fieldDef.SetValue(to, entry.Value);
1873+
}
1874+
return to;
1875+
}
1876+
1877+
private static ObjectDictionaryDefinition CreateObjectDictionaryDefinition(Type type)
1878+
{
1879+
var def = new ObjectDictionaryDefinition
1880+
{
1881+
Type = type,
1882+
};
1883+
1884+
foreach (var pi in type.GetSerializableProperties())
1885+
{
1886+
def.Add(pi.Name, new ObjectDictionaryFieldDefinition
1887+
{
1888+
Name = pi.Name,
1889+
Type = pi.PropertyType,
1890+
GetValueFn = pi.GetPropertyGetterFn(),
1891+
SetValueFn = pi.GetPropertySetterFn(),
1892+
});
17801893
}
17811894

17821895
if (JsConfig.IncludePublicFields)
17831896
{
1784-
foreach (var fi in obj.GetType().GetSerializableFields())
1897+
foreach (var fi in type.GetSerializableFields())
17851898
{
1786-
dict[fi.Name] = fi.GetValue(obj);
1899+
def.Add(fi.Name, new ObjectDictionaryFieldDefinition
1900+
{
1901+
Name = fi.Name,
1902+
Type = fi.FieldType,
1903+
GetValueFn = fi.GetFieldGetterFn(),
1904+
SetValueFn = fi.GetFieldSetterFn(),
1905+
});
17871906
}
17881907
}
1789-
return dict;
1908+
return def;
17901909
}
17911910
}
17921911

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using System.Collections.Generic;
2+
using NUnit.Framework;
3+
4+
namespace ServiceStack.Text.Tests
5+
{
6+
[TestFixture]
7+
public class AutoMappingObjectDictionaryTests
8+
{
9+
[Test]
10+
public void Can_convert_Car_to_ObjectDictionary()
11+
{
12+
var dto = new Car { Age = 10, Name = "ZCar" };
13+
var map = dto.ToObjectDictionary();
14+
15+
Assert.That(map["Age"], Is.EqualTo(dto.Age));
16+
Assert.That(map["Name"], Is.EqualTo(dto.Name));
17+
18+
var fromDict = (Car)map.FromObjectDictionary(typeof(Car));
19+
Assert.That(fromDict.Age, Is.EqualTo(dto.Age));
20+
Assert.That(fromDict.Name, Is.EqualTo(dto.Name));
21+
}
22+
23+
[Test]
24+
public void Can_convert_Cart_to_ObjectDictionary()
25+
{
26+
var dto = new User
27+
{
28+
FirstName = "First",
29+
LastName = "Last",
30+
Car = new Car { Age = 10, Name = "ZCar" },
31+
};
32+
33+
var map = dto.ToObjectDictionary();
34+
35+
Assert.That(map["FirstName"], Is.EqualTo(dto.FirstName));
36+
Assert.That(map["LastName"], Is.EqualTo(dto.LastName));
37+
Assert.That(((Car)map["Car"]).Age, Is.EqualTo(dto.Car.Age));
38+
Assert.That(((Car)map["Car"]).Name, Is.EqualTo(dto.Car.Name));
39+
40+
var fromDict = (User)map.FromObjectDictionary(typeof(User));
41+
Assert.That(fromDict.FirstName, Is.EqualTo(dto.FirstName));
42+
Assert.That(fromDict.LastName, Is.EqualTo(dto.LastName));
43+
Assert.That(fromDict.Car.Age, Is.EqualTo(dto.Car.Age));
44+
Assert.That(fromDict.Car.Name, Is.EqualTo(dto.Car.Name));
45+
}
46+
47+
[Test]
48+
public void Can_Convert_from_ObjectDictionary_with_Different_Types()
49+
{
50+
var map = new Dictionary<string, object>
51+
{
52+
{ "FirstName", 1 },
53+
{ "LastName", true },
54+
{ "Car", new SubCar { Age = 10, Name = "SubCar", Custom = "Custom"} },
55+
};
56+
57+
var fromDict = (User)map.FromObjectDictionary(typeof(User));
58+
Assert.That(fromDict.FirstName, Is.EqualTo("1"));
59+
Assert.That(fromDict.LastName, Is.EqualTo(bool.TrueString));
60+
Assert.That(fromDict.Car.Age, Is.EqualTo(10));
61+
Assert.That(fromDict.Car.Name, Is.EqualTo("SubCar"));
62+
}
63+
}
64+
}

tests/ServiceStack.Text.Tests/AutoMappingTests.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ public class Car
3333
public int Age { get; set; }
3434
}
3535

36+
public class SubCar : Car
37+
{
38+
public string Custom { get; set; }
39+
}
40+
3641
public class UserDto
3742
{
3843
public string FirstName { get; set; }

tests/ServiceStack.Text.Tests/ServiceStack.Text.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@
176176
</Reference>
177177
</ItemGroup>
178178
<ItemGroup>
179+
<Compile Include="AutoMappingObjectDictionaryTests.cs" />
179180
<Compile Include="EnumerableTests.cs" />
180181
<Compile Include="AttributeTests.cs" />
181182
<Compile Include="CsvTypeTests.cs" />

0 commit comments

Comments
 (0)