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

Commit bf2483d

Browse files
committed
Add better support for mapping collections of different types
1 parent 5dae03a commit bf2483d

File tree

5 files changed

+289
-78
lines changed

5 files changed

+289
-78
lines changed

src/ServiceStack.Text/AutoMappingUtils.cs

Lines changed: 123 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.Runtime.Serialization;
1111
using System.Threading;
1212
using ServiceStack.Text;
13+
using ServiceStack.Text.Json;
1314

1415
namespace ServiceStack
1516
{
@@ -18,8 +19,16 @@ public class CustomHttpResult { }
1819

1920
public static class AutoMappingUtils
2021
{
21-
public static T ConvertTo<T>(this object from)
22+
public static T ConvertTo<T>(this object from)
2223
{
24+
if (typeof(IEnumerable).IsAssignableFromType(typeof(T)))
25+
{
26+
var listResult = TranslateListWithElements.TryTranslateCollections(
27+
from.GetType(), typeof(T), from);
28+
29+
return (T)listResult;
30+
}
31+
2332
var to = typeof(T).CreateInstance<T>();
2433
return to.PopulateWith(from);
2534
}
@@ -100,7 +109,7 @@ private static object PopulateObjectInternal(object obj, Dictionary<Type, int> r
100109
}
101110
return obj;
102111
}
103-
112+
104113
private static Dictionary<Type, object> DefaultValueTypes = new Dictionary<Type, object>();
105114

106115
public static object GetDefaultValue(this Type type)
@@ -130,7 +139,7 @@ private static readonly ConcurrentDictionary<string, AssignmentDefinition> Assig
130139

131140
internal static AssignmentDefinition GetAssignmentDefinition(Type toType, Type fromType)
132141
{
133-
var cacheKey = toType.FullName + "<" + fromType.FullName;
142+
var cacheKey = CreateCacheKey(fromType, toType);
134143

135144
return AssignmentDefinitionCache.GetOrAdd(cacheKey, delegate
136145
{
@@ -157,6 +166,12 @@ internal static AssignmentDefinition GetAssignmentDefinition(Type toType, Type f
157166
});
158167
}
159168

169+
internal static string CreateCacheKey(Type fromType, Type toType)
170+
{
171+
var cacheKey = fromType.FullName + ">" + toType.FullName;
172+
return cacheKey;
173+
}
174+
160175
private static Dictionary<string, AssignmentMember> GetMembers(Type type, bool isReadable)
161176
{
162177
var map = new Dictionary<string, AssignmentMember>();
@@ -585,82 +600,45 @@ public void Populate(object to, object from,
585600
Func<PropertyInfo, bool> propertyInfoPredicate,
586601
Func<object, Type, bool> valuePredicate)
587602
{
588-
foreach (var assignmentEntry in AssignmentMemberMap)
603+
foreach (var assignmentEntryMap in AssignmentMemberMap)
589604
{
590-
var assignmentMember = assignmentEntry.Value;
591-
var fromMember = assignmentEntry.Value.From;
592-
var toMember = assignmentEntry.Value.To;
605+
var assignmentEntry = assignmentEntryMap.Value;
606+
var fromMember = assignmentEntry.From;
607+
var toMember = assignmentEntry.To;
593608

594609
if (fromMember.PropertyInfo != null && propertyInfoPredicate != null)
595610
{
596611
if (!propertyInfoPredicate(fromMember.PropertyInfo)) continue;
597612
}
598613

614+
var fromType = fromMember.Type;
615+
var toType = toMember.Type;
599616
try
600617
{
601-
var fromValue = assignmentMember.GetValueFn(from);
618+
var fromValue = assignmentEntry.GetValueFn(from);
602619

603620
if (valuePredicate != null)
604621
{
605622
if (!valuePredicate(fromValue, fromMember.PropertyInfo.PropertyType)) continue;
606623
}
607624

608-
if (fromMember.Type != toMember.Type)
625+
if (fromType != toType)
609626
{
610-
if (fromMember.Type == typeof(string))
611-
{
612-
fromValue = TypeSerializer.DeserializeFromString((string)fromValue, toMember.Type);
613-
}
614-
else if (toMember.Type == typeof(string))
615-
{
616-
fromValue = TypeSerializer.SerializeToString(fromValue);
617-
}
618-
else if (toMember.Type.IsEnum() || fromMember.Type.IsEnum())
619-
{
620-
if (toMember.Type.IsEnum() && fromMember.Type.IsEnum())
621-
{
622-
fromValue = Enum.Parse(toMember.Type, fromValue.ToString());
623-
}
624-
else if (toMember.Type.IsNullableType())
625-
{
626-
var genericArg = toMember.Type.GenericTypeArguments()[0];
627-
if (genericArg.IsEnum())
628-
{
629-
fromValue = Enum.ToObject(genericArg, fromValue);
630-
}
631-
}
632-
else if (toMember.Type.IsIntegerType())
633-
{
634-
fromValue = Enum.ToObject(fromMember.Type, fromValue);
635-
}
636-
}
637-
else if (typeof(IEnumerable).IsAssignableFrom(fromMember.Type))
638-
{
639-
var listResult = TranslateListWithElements.TryTranslateToGenericICollection(
640-
fromMember.Type, toMember.Type, fromValue);
641-
642-
if (listResult != null)
643-
{
644-
fromValue = listResult;
645-
}
646-
}
647-
else if (!(toMember.Type.IsValueType()
648-
|| toMember.Type.IsNullableType()))
627+
var converterFn = TypeConverter.GetTypeConverter(fromType, toType);
628+
if (converterFn != null)
649629
{
650-
var toValue = toMember.Type.CreateInstance();
651-
toValue.PopulateWith(fromValue);
652-
fromValue = toValue;
630+
fromValue = converterFn(fromValue);
653631
}
654632
}
655633

656-
var setterFn = assignmentMember.SetValueFn;
634+
var setterFn = assignmentEntry.SetValueFn;
657635
setterFn(to, fromValue);
658636
}
659637
catch (Exception ex)
660638
{
661639
Tracer.Instance.WriteWarning("Error trying to set properties {0}.{1} > {2}.{3}:\n{4}",
662-
FromType.FullName, fromMember.Type.Name,
663-
ToType.FullName, toMember.Type.Name, ex);
640+
FromType.FullName, fromType.Name,
641+
ToType.FullName, toType.Name, ex);
664642
}
665643
}
666644
}
@@ -694,4 +672,95 @@ public static PropertyGetterDelegate GetFieldGetterFn(this FieldInfo fieldInfo)
694672
return PclExport.Instance.GetFieldGetterFn(fieldInfo);
695673
}
696674
}
675+
676+
internal static class TypeConverter
677+
{
678+
internal static ConcurrentDictionary<string, PropertyGetterDelegate> TypeConvertersCache
679+
= new ConcurrentDictionary<string, PropertyGetterDelegate>();
680+
681+
public static PropertyGetterDelegate GetTypeConverter(Type fromType, Type toType)
682+
{
683+
var cacheKey = AutoMappingUtils.CreateCacheKey(fromType, toType);
684+
685+
return TypeConvertersCache.GetOrAdd(cacheKey,
686+
(Func<string, PropertyGetterDelegate>)(key => CreateTypeConverter(fromType, toType)));
687+
}
688+
689+
public static PropertyGetterDelegate CreateTypeConverter(Type fromType, Type toType)
690+
{
691+
if (fromType == toType)
692+
return null;
693+
694+
if (fromType == typeof(string))
695+
{
696+
return fromValue => TypeSerializer.DeserializeFromString((string)fromValue, toType);
697+
}
698+
if (toType == typeof(string))
699+
{
700+
return TypeSerializer.SerializeToString;
701+
}
702+
if (toType.IsEnum() || fromType.IsEnum())
703+
{
704+
if (toType.IsEnum() && fromType.IsEnum())
705+
{
706+
return fromValue => Enum.Parse(toType, fromValue.ToString());
707+
}
708+
if (toType.IsNullableType())
709+
{
710+
var genericArg = toType.GenericTypeArguments()[0];
711+
if (genericArg.IsEnum())
712+
{
713+
return fromValue => Enum.ToObject(genericArg, fromValue);
714+
}
715+
}
716+
else if (toType.IsIntegerType())
717+
{
718+
return fromValue => Enum.ToObject(fromType, fromValue);
719+
}
720+
}
721+
else if (toType.IsNullableType())
722+
{
723+
return null;
724+
}
725+
else if (typeof(IEnumerable).IsAssignableFrom(fromType))
726+
{
727+
return fromValue =>
728+
{
729+
var listResult = TranslateListWithElements.TryTranslateCollections(
730+
fromType, toType, fromValue);
731+
732+
return listResult ?? fromValue;
733+
};
734+
}
735+
else if (toType.IsValueType())
736+
{
737+
return fromValue => Convert.ChangeType(fromValue, toType);
738+
}
739+
else
740+
{
741+
return fromValue =>
742+
{
743+
var toValue = toType.CreateInstance();
744+
toValue.PopulateWith(fromValue);
745+
return toValue;
746+
};
747+
}
748+
749+
return null;
750+
}
751+
}
752+
753+
internal class TypeConverter<From, To>
754+
{
755+
static TypeConverter()
756+
{
757+
ConvertFn = TypeConverter.CreateTypeConverter(typeof(From), typeof(To));
758+
}
759+
760+
public static PropertyGetterDelegate ConvertFn;
761+
public PropertyGetterDelegate GetConvertFn()
762+
{
763+
return ConvertFn;
764+
}
765+
}
697766
}

src/ServiceStack.Text/TranslateListWithElements.cs

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -76,19 +76,19 @@ public static object TranslateToConvertibleGenericICollectionCache(
7676
return translateToFn(from, toInstanceOfType);
7777
}
7878

79-
public static object TryTranslateToGenericICollection(Type fromPropertyType, Type toPropertyType, object fromValue)
79+
public static object TryTranslateCollections(Type fromPropertyType, Type toPropertyType, object fromValue)
8080
{
81-
var args = typeof(ICollection<>).GetGenericArgumentsIfBothHaveSameGenericDefinitionTypeAndArguments(
81+
var args = typeof(IEnumerable<>).GetGenericArgumentsIfBothHaveSameGenericDefinitionTypeAndArguments(
8282
fromPropertyType, toPropertyType);
8383

84-
if (args != null)
84+
if (args != null)
8585
{
8686
return TranslateToGenericICollectionCache(
8787
fromValue, toPropertyType, args[0]);
88-
}
88+
}
8989

90-
var varArgs = typeof(ICollection<>).GetGenericArgumentsIfBothHaveConvertibleGenericDefinitionTypeAndArguments(
91-
fromPropertyType, toPropertyType);
90+
var varArgs = typeof(IEnumerable<>).GetGenericArgumentsIfBothHaveConvertibleGenericDefinitionTypeAndArguments(
91+
fromPropertyType, toPropertyType);
9292

9393
if (varArgs != null)
9494
{
@@ -150,16 +150,16 @@ public static object CreateInstance(Type toInstanceOfType)
150150
if (toInstanceOfType.HasAnyTypeDefinitionsOf(
151151
typeof(ICollection<>), typeof(IList<>)))
152152
{
153-
return ReflectionExtensions.CreateInstance(typeof(List<T>));
153+
return typeof(List<T>).CreateInstance();
154154
}
155155
}
156156

157-
return ReflectionExtensions.CreateInstance(toInstanceOfType);
157+
return toInstanceOfType.CreateInstance();
158158
}
159159

160160
public static IList TranslateToIList(IList fromList, Type toInstanceOfType)
161161
{
162-
var to = (IList)ReflectionExtensions.CreateInstance(toInstanceOfType);
162+
var to = (IList)toInstanceOfType.CreateInstance();
163163
foreach (var item in fromList)
164164
{
165165
to.Add(item);
@@ -170,7 +170,15 @@ public static IList TranslateToIList(IList fromList, Type toInstanceOfType)
170170
public static object LateBoundTranslateToGenericICollection(
171171
object fromList, Type toInstanceOfType)
172172
{
173-
if (fromList == null) return null; //AOT
173+
if (fromList == null)
174+
return null; //AOT
175+
176+
if (toInstanceOfType.IsArray)
177+
{
178+
var result = TranslateToGenericICollection(
179+
(ICollection<T>)fromList, typeof(List<T>));
180+
return result.ToArray();
181+
}
174182

175183
return TranslateToGenericICollection(
176184
(ICollection<T>)fromList, toInstanceOfType);

tests/ServiceStack.Text.Tests/AutoMappingBenchmarks.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ public void Does_Convert_BenchSource()
170170
{
171171
var from = new BenchSource();
172172
var to = from.ConvertTo<BenchDestination>();
173+
to = from.ConvertTo<BenchDestination>();
173174

174175
using (JsConfig.With(includePublicFields: true))
175176
{

0 commit comments

Comments
 (0)