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

Commit 23625fc

Browse files
committed
Better support for KVPs
1 parent 96aa019 commit 23625fc

File tree

3 files changed

+132
-26
lines changed

3 files changed

+132
-26
lines changed

src/ServiceStack.Text/AutoMappingUtils.cs

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -248,15 +248,16 @@ public static object ChangeValueType(object from, Type toType)
248248
return Convert.ChangeType(from, toType, provider: null);
249249
}
250250

251-
var toKvpType = toType.GetTypeWithGenericTypeDefinitionOf(typeof(KeyValuePair<,>));
252-
if (toKvpType != null)
251+
var fromKvpType = fromType.GetTypeWithGenericTypeDefinitionOf(typeof(KeyValuePair<,>));
252+
if (fromKvpType != null)
253253
{
254-
var fromKvpType = fromType.GetTypeWithGenericTypeDefinitionOf(typeof(KeyValuePair<,>));
255-
if (fromKvpType != null)
254+
var fromProps = TypeProperties.Get(fromKvpType);
255+
var fromKey = fromProps.GetPublicGetter("Key")(from);
256+
var fromValue = fromProps.GetPublicGetter("Value")(from);
257+
258+
var toKvpType = toType.GetTypeWithGenericTypeDefinitionOf(typeof(KeyValuePair<,>));
259+
if (toKvpType != null)
256260
{
257-
var fromProps = TypeProperties.Get(fromKvpType);
258-
var fromKey = fromProps.GetPublicGetter("Key")(from);
259-
var fromValue = fromProps.GetPublicGetter("Value")(from);
260261

261262
var toKvpArgs = toKvpType.GetGenericArguments();
262263
var toKeyType = toKvpArgs[0];
@@ -267,6 +268,18 @@ public static object ChangeValueType(object from, Type toType)
267268
var to = toCtor.Invoke(new[] {toKey,toValue});
268269
return to;
269270
}
271+
272+
if (typeof(IDictionary).IsAssignableFrom(toType))
273+
{
274+
var genericDef = toType.GetTypeWithGenericTypeDefinitionOf(typeof(IDictionary<,>));
275+
var toArgs = genericDef.GetGenericArguments();
276+
var toKeyType = toArgs[0];
277+
var toValueType = toArgs[1];
278+
279+
var to = (IDictionary)toType.CreateInstance();
280+
to[fromKey.ConvertTo(toKeyType)] = fromValue.ConvertTo(toValueType);
281+
return to;
282+
}
270283
}
271284

272285
return TypeSerializer.DeserializeFromString(from.ToJsv(), toType);
@@ -887,7 +900,7 @@ public static object TryConvertCollections(Type fromType, Type toType, object fr
887900

888901

889902
// Fallback for handling any KVP combo
890-
var toKvpDefType = toType.GetKeyValuePairTypeDef();
903+
var toKvpDefType = toType.GetKeyValuePairsTypeDef();
891904
switch (obj) {
892905
case IDictionary toDict:
893906
{

src/ServiceStack.Text/PlatformExtensions.cs

Lines changed: 71 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -676,9 +676,9 @@ public static Dictionary<string, object> ToObjectDictionary(this object obj)
676676
if (obj is IDictionary<string, object> interfaceDict)
677677
return new Dictionary<string, object>(interfaceDict);
678678

679+
var to = new Dictionary<string, object>();
679680
if (obj is Dictionary<string, string> stringDict)
680681
{
681-
var to = new Dictionary<string, object>();
682682
foreach (var entry in stringDict)
683683
{
684684
to[entry.Key] = entry.Value;
@@ -688,7 +688,6 @@ public static Dictionary<string, object> ToObjectDictionary(this object obj)
688688

689689
if (obj is IDictionary d)
690690
{
691-
var to = new Dictionary<string, object>();
692691
foreach (var key in d.Keys)
693692
{
694693
to[key.ToString()] = d[key];
@@ -698,56 +697,110 @@ public static Dictionary<string, object> ToObjectDictionary(this object obj)
698697

699698
if (obj is NameValueCollection nvc)
700699
{
701-
var to = new Dictionary<string, object>();
702700
for (var i = 0; i < nvc.Count; i++)
703701
{
704702
to[nvc.GetKey(i)] = nvc.Get(i);
705703
}
706704
return to;
707705
}
708706

707+
if (obj is IEnumerable<KeyValuePair<string, object>> objKvps)
708+
{
709+
foreach (var kvp in objKvps)
710+
{
711+
to[kvp.Key] = kvp.Value;
712+
}
713+
return to;
714+
}
715+
if (obj is IEnumerable<KeyValuePair<string, string>> strKvps)
716+
{
717+
foreach (var kvp in strKvps)
718+
{
719+
to[kvp.Key] = kvp.Value;
720+
}
721+
return to;
722+
}
723+
709724
var type = obj.GetType();
725+
if (type.GetKeyValuePairsTypes(out var keyType, out var valueType, out var kvpType) && obj is IEnumerable e)
726+
{
727+
var keyGetter = TypeProperties.Get(kvpType).GetPublicGetter("Key");
728+
var valueGetter = TypeProperties.Get(kvpType).GetPublicGetter("Value");
729+
730+
foreach (var entry in e)
731+
{
732+
var key = keyGetter(entry);
733+
var value = valueGetter(entry);
734+
to[key.ConvertTo<string>()] = value;
735+
}
736+
return to;
737+
}
738+
739+
740+
if (obj is KeyValuePair<string, object> objKvp)
741+
return new Dictionary<string, object> { {objKvp.Key, objKvp.Value} };
742+
if (obj is KeyValuePair<string, string> strKvp)
743+
return new Dictionary<string, object> { {strKvp.Key, strKvp.Value} };
744+
745+
if (type.GetKeyValuePairTypes(out _, out var _))
746+
{
747+
return new Dictionary<string, object> {
748+
{ TypeProperties.Get(type).GetPublicGetter("Key")(obj).ConvertTo<string>(), TypeProperties.Get(type).GetPublicGetter("Value")(obj) },
749+
};
750+
}
710751

711752
if (!toObjectMapCache.TryGetValue(type, out var def))
712753
toObjectMapCache[type] = def = CreateObjectDictionaryDefinition(type);
713754

714-
var dict = new Dictionary<string, object>();
715-
716755
foreach (var fieldDef in def.Fields)
717756
{
718-
dict[fieldDef.Name] = fieldDef.GetValueFn(obj);
757+
to[fieldDef.Name] = fieldDef.GetValueFn(obj);
719758
}
720759

721-
return dict;
760+
return to;
722761
}
723762

724-
public static Type GetKeyValuePairTypeDef(this Type dictType)
763+
public static Type GetKeyValuePairsTypeDef(this Type dictType)
725764
{
726765
//matches IDictionary<,>, IReadOnlyDictionary<,>, List<KeyValuePair<string, object>>
727766
var genericDef = dictType.GetTypeWithGenericTypeDefinitionOf(typeof(IEnumerable<>));
728767
if (genericDef == null)
729768
return null;
730769

731770
var genericEnumType = genericDef.GetGenericArguments()[0];
732-
return genericEnumType.GetTypeWithGenericTypeDefinitionOf(typeof(KeyValuePair<,>));
771+
return GetKeyValuePairTypeDef(genericEnumType);
733772
}
773+
774+
public static Type GetKeyValuePairTypeDef(this Type genericEnumType) => genericEnumType.GetTypeWithGenericTypeDefinitionOf(typeof(KeyValuePair<,>));
775+
776+
public static bool GetKeyValuePairsTypes(this Type dictType, out Type keyType, out Type valueType) =>
777+
dictType.GetKeyValuePairsTypes(out keyType, out valueType, out _);
734778

735-
public static bool GetKeyValuePairsTypes(this Type dictType, out Type keyType, out Type valueType)
779+
public static bool GetKeyValuePairsTypes(this Type dictType, out Type keyType, out Type valueType, out Type kvpType)
736780
{
737781
//matches IDictionary<,>, IReadOnlyDictionary<,>, List<KeyValuePair<string, object>>
738782
var genericDef = dictType.GetTypeWithGenericTypeDefinitionOf(typeof(IEnumerable<>));
739783
if (genericDef != null)
740784
{
741-
var genericEnumType = genericDef.GetGenericArguments()[0];
742-
var genericKvps = genericEnumType.GetTypeWithGenericTypeDefinitionOf(typeof(KeyValuePair<,>));
743-
if (genericKvps != null)
744-
{
745-
var genericArgs = genericEnumType.GetGenericArguments();
746-
keyType = genericArgs[0];
747-
valueType = genericArgs[1];
785+
kvpType = genericDef.GetGenericArguments()[0];
786+
if (GetKeyValuePairTypes(kvpType, out keyType, out valueType))
748787
return true;
749-
}
750788
}
789+
kvpType = keyType = valueType = null;
790+
return false;
791+
}
792+
793+
private static bool GetKeyValuePairTypes(this Type kvpType, out Type keyType, out Type valueType)
794+
{
795+
var genericKvps = kvpType.GetTypeWithGenericTypeDefinitionOf(typeof(KeyValuePair<,>));
796+
if (genericKvps != null)
797+
{
798+
var genericArgs = kvpType.GetGenericArguments();
799+
keyType = genericArgs[0];
800+
valueType = genericArgs[1];
801+
return true;
802+
}
803+
751804
keyType = valueType = null;
752805
return false;
753806
}

tests/ServiceStack.Text.Tests/AutoMappingTests.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1243,5 +1243,45 @@ public void Can_convert_between_Poco_and_ObjectDictionary()
12431243
{"Name", "foo"},
12441244
}.ConvertTo<Person>(), Is.EqualTo(person));
12451245
}
1246+
1247+
[Test]
1248+
public void Can_ToObjectDictionary_KVPs()
1249+
{
1250+
var objKvp = new KeyValuePair<string, object>("A", 1);
1251+
var strKvp = new KeyValuePair<string, string>("A", "1");
1252+
var intKvp = new KeyValuePair<string, int>("A", 1);
1253+
1254+
Assert.That(objKvp.ToObjectDictionary(), Is.EquivalentTo(new Dictionary<string, object> { {"A", 1} }));
1255+
Assert.That(strKvp.ToObjectDictionary(), Is.EquivalentTo(new Dictionary<string, object> { {"A", "1"} }));
1256+
Assert.That(intKvp.ToObjectDictionary(), Is.EquivalentTo(new Dictionary<string, object> { {"A", 1} }));
1257+
1258+
var objKvp2 = new KeyValuePair<string, object>("B", 2);
1259+
var strKvp2 = new KeyValuePair<string, string>("B", "2");
1260+
var intKvp2 = new KeyValuePair<string, int>("B", 2);
1261+
1262+
Assert.That(new[] { objKvp, objKvp2 }.ToObjectDictionary(), Is.EquivalentTo(new Dictionary<string, object> { {"A", 1}, {"B", 2} }));
1263+
Assert.That(new[] { strKvp, strKvp2 }.ToObjectDictionary(), Is.EquivalentTo(new Dictionary<string, object> { {"A", "1"}, {"B", "2"} }));
1264+
Assert.That(new[] { intKvp, intKvp2 }.ToObjectDictionary(), Is.EquivalentTo(new Dictionary<string, object> { {"A", 1}, {"B", 2} }));
1265+
}
1266+
1267+
[Test]
1268+
public void Can_convert_KVPs_to_ObjectDictionary()
1269+
{
1270+
var objKvp = new KeyValuePair<string, object>("A", 1);
1271+
var strKvp = new KeyValuePair<string, string>("A", "1");
1272+
var intKvp = new KeyValuePair<string, int>("A", 1);
1273+
1274+
Assert.That(objKvp.ConvertTo<Dictionary<string,object>>(), Is.EquivalentTo(new Dictionary<string, object> { {"A", 1} }));
1275+
Assert.That(strKvp.ConvertTo<Dictionary<string,object>>(), Is.EquivalentTo(new Dictionary<string, object> { {"A", "1"} }));
1276+
Assert.That(intKvp.ConvertTo<Dictionary<string,object>>(), Is.EquivalentTo(new Dictionary<string, object> { {"A", 1} }));
1277+
1278+
var objKvp2 = new KeyValuePair<string, object>("B", 2);
1279+
var strKvp2 = new KeyValuePair<string, string>("B", "2");
1280+
var intKvp2 = new KeyValuePair<string, int>("B", 2);
1281+
1282+
Assert.That(new[] { objKvp, objKvp2 }.ConvertTo<Dictionary<string,object>>(), Is.EquivalentTo(new Dictionary<string, object> { {"A", 1}, {"B", 2} }));
1283+
Assert.That(new[] { strKvp, strKvp2 }.ConvertTo<Dictionary<string,object>>(), Is.EquivalentTo(new Dictionary<string, object> { {"A", "1"}, {"B", "2"} }));
1284+
Assert.That(new[] { intKvp, intKvp2 }.ConvertTo<Dictionary<string,object>>(), Is.EquivalentTo(new Dictionary<string, object> { {"A", 1}, {"B", 2} }));
1285+
}
12461286
}
12471287
}

0 commit comments

Comments
 (0)