Skip to content

Commit 4393f63

Browse files
authored
Implement deserialization of dictionaries using primitives as keys (#44)
1 parent a572b42 commit 4393f63

File tree

3 files changed

+82
-2
lines changed

3 files changed

+82
-2
lines changed

Tomlet.Tests/DictionaryTests.cs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Collections.Generic;
1+
using System;
2+
using System.Collections.Generic;
23
using Tomlet.Models;
34
using Tomlet.Tests.TestModelClasses;
45
using Xunit;
@@ -53,4 +54,40 @@ public void DictionaryKeysShouldBeProperlyEscaped()
5354
Assert.Contains(expectedValue, serialized);
5455
}
5556
}
57+
58+
private bool PrimitiveKeyTestHelper<T>(params T[] values) where T : unmanaged, IConvertible
59+
{
60+
var primitiveDict = new Dictionary<T, string>();
61+
for (int i=0; i<values.Length; i++) {
62+
T val = values[i];
63+
primitiveDict[val] = $"Test {i+1}";
64+
}
65+
66+
var serialized = TomletMain.TomlStringFrom(primitiveDict);
67+
68+
var deserialized = TomletMain.To<Dictionary<T, string>>(serialized);
69+
70+
foreach (var (key, value) in primitiveDict) {
71+
if (!deserialized.ContainsKey(key)) {
72+
return false;
73+
}
74+
if (deserialized[key] != value) {
75+
return false;
76+
}
77+
}
78+
return true;
79+
}
80+
81+
[Fact]
82+
public void PrimitiveDictionaryKeysShouldWork()
83+
{
84+
Assert.True(PrimitiveKeyTestHelper(true, false));
85+
Assert.True(PrimitiveKeyTestHelper(long.MaxValue, long.MinValue, 0, 4736251));
86+
Assert.True(PrimitiveKeyTestHelper(uint.MinValue, uint.MaxValue, 0u, 1996u));
87+
88+
// \n causes an exception when deserializing
89+
// I don't consider this a bug with the primitive dict deserializer because the string dict deserializer also has this issue
90+
Assert.True(PrimitiveKeyTestHelper('a', 'b', 'c' /*, '\n' */));
91+
}
92+
5693
}

Tomlet/Extensions/ReflectionExtensions.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,22 @@ internal static bool TryGetBestMatchConstructor(this Type type, out ConstructorI
3232
bestMatchConstructor = parameterizedConstructors.Single();
3333
return true;
3434
}
35+
36+
internal static bool IsIntegerType(this Type type) {
37+
switch (Type.GetTypeCode(type)) {
38+
case TypeCode.Byte:
39+
case TypeCode.SByte:
40+
case TypeCode.UInt16:
41+
case TypeCode.Int16:
42+
case TypeCode.UInt32:
43+
case TypeCode.Int32:
44+
case TypeCode.UInt64:
45+
case TypeCode.Int64:
46+
return true;
47+
default:
48+
return false;
49+
}
50+
}
51+
3552
}
3653
}

Tomlet/TomlSerializationMethods.cs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
using System;
22
using System.Collections;
33
using System.Collections.Generic;
4+
using System.Globalization;
45
using System.Linq;
56
using System.Reflection;
67
using Tomlet.Attributes;
78
using Tomlet.Exceptions;
9+
using Tomlet.Extensions;
810
using Tomlet.Models;
911

1012
namespace Tomlet
1113
{
1214
public static class TomlSerializationMethods
1315
{
1416
private static MethodInfo _stringKeyedDictionaryMethod = typeof(TomlSerializationMethods).GetMethod(nameof(StringKeyedDictionaryDeserializerFor), BindingFlags.Static | BindingFlags.NonPublic)!;
17+
private static MethodInfo _primitiveKeyedDictionaryMethod = typeof(TomlSerializationMethods).GetMethod(nameof(PrimitiveKeyedDictionaryDeserializerFor), BindingFlags.Static | BindingFlags.NonPublic)!;
1518
private static MethodInfo _genericDictionarySerializerMethod = typeof(TomlSerializationMethods).GetMethod(nameof(GenericDictionarySerializer), BindingFlags.Static | BindingFlags.NonPublic)!;
1619
private static MethodInfo _genericNullableSerializerMethod = typeof(TomlSerializationMethods).GetMethod(nameof(GenericNullableSerializer), BindingFlags.Static | BindingFlags.NonPublic)!;
1720

@@ -195,6 +198,12 @@ internal static Deserialize<object> GetDeserializer(Type t, TomlSerializerOption
195198
{
196199
return (Deserialize<object>)_stringKeyedDictionaryMethod.MakeGenericMethod(genericArgs[1]).Invoke(null, new object[]{options})!;
197200
}
201+
202+
if (genericArgs[0].IsIntegerType() || genericArgs[0] == typeof(bool) || genericArgs[0] == typeof(char))
203+
{
204+
// float primitives not supported due to decimal point causing issues
205+
return (Deserialize<object>)_primitiveKeyedDictionaryMethod.MakeGenericMethod(genericArgs).Invoke(null, new object[]{options})!;
206+
}
198207
}
199208

200209
return TomlCompositeDeserializer.For(t, options);
@@ -279,7 +288,24 @@ private static Deserialize<Dictionary<string, T>> StringKeyedDictionaryDeseriali
279288
return table.Entries.ToDictionary(entry => entry.Key, entry => (T)deserializer(entry.Value));
280289
};
281290
}
282-
291+
292+
// unmanaged + IConvertible is the closest I can get to expressing "primitives only"
293+
private static Deserialize<Dictionary<TKey, TValue>> PrimitiveKeyedDictionaryDeserializerFor<TKey, TValue>(TomlSerializerOptions options) where TKey : unmanaged, IConvertible
294+
{
295+
var valueDeserializer = GetDeserializer(typeof(TValue), options);
296+
297+
return value =>
298+
{
299+
if (value is not TomlTable table)
300+
throw new TomlTypeMismatchException(typeof(TomlTable), value.GetType(), typeof(Dictionary<TKey, TValue>));
301+
302+
return table.Entries.ToDictionary(
303+
entry => (TKey)(entry.Key as IConvertible).ToType(typeof(TKey), CultureInfo.InvariantCulture),
304+
entry => (TValue)valueDeserializer(entry.Value)
305+
);
306+
};
307+
}
308+
283309
private static TomlValue? GenericNullableSerializer<T>(T? nullable, TomlSerializerOptions options) where T : struct
284310
{
285311
var elementSerializer = GetSerializer(typeof(T), options);

0 commit comments

Comments
 (0)