Skip to content

Commit

Permalink
Implement deserialization of dictionaries using primitives as keys (#44)
Browse files Browse the repository at this point in the history
  • Loading branch information
Sprixitite authored Aug 12, 2024
1 parent a572b42 commit 4393f63
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 2 deletions.
39 changes: 38 additions & 1 deletion Tomlet.Tests/DictionaryTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using Tomlet.Models;
using Tomlet.Tests.TestModelClasses;
using Xunit;
Expand Down Expand Up @@ -53,4 +54,40 @@ public void DictionaryKeysShouldBeProperlyEscaped()
Assert.Contains(expectedValue, serialized);
}
}

private bool PrimitiveKeyTestHelper<T>(params T[] values) where T : unmanaged, IConvertible
{
var primitiveDict = new Dictionary<T, string>();
for (int i=0; i<values.Length; i++) {
T val = values[i];
primitiveDict[val] = $"Test {i+1}";
}

var serialized = TomletMain.TomlStringFrom(primitiveDict);

var deserialized = TomletMain.To<Dictionary<T, string>>(serialized);

foreach (var (key, value) in primitiveDict) {
if (!deserialized.ContainsKey(key)) {
return false;
}
if (deserialized[key] != value) {
return false;
}
}
return true;
}

[Fact]
public void PrimitiveDictionaryKeysShouldWork()
{
Assert.True(PrimitiveKeyTestHelper(true, false));
Assert.True(PrimitiveKeyTestHelper(long.MaxValue, long.MinValue, 0, 4736251));
Assert.True(PrimitiveKeyTestHelper(uint.MinValue, uint.MaxValue, 0u, 1996u));

// \n causes an exception when deserializing
// I don't consider this a bug with the primitive dict deserializer because the string dict deserializer also has this issue
Assert.True(PrimitiveKeyTestHelper('a', 'b', 'c' /*, '\n' */));
}

}
17 changes: 17 additions & 0 deletions Tomlet/Extensions/ReflectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,22 @@ internal static bool TryGetBestMatchConstructor(this Type type, out ConstructorI
bestMatchConstructor = parameterizedConstructors.Single();
return true;
}

internal static bool IsIntegerType(this Type type) {
switch (Type.GetTypeCode(type)) {
case TypeCode.Byte:
case TypeCode.SByte:
case TypeCode.UInt16:
case TypeCode.Int16:
case TypeCode.UInt32:
case TypeCode.Int32:
case TypeCode.UInt64:
case TypeCode.Int64:
return true;
default:
return false;
}
}

}
}
28 changes: 27 additions & 1 deletion Tomlet/TomlSerializationMethods.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using Tomlet.Attributes;
using Tomlet.Exceptions;
using Tomlet.Extensions;
using Tomlet.Models;

namespace Tomlet
{
public static class TomlSerializationMethods
{
private static MethodInfo _stringKeyedDictionaryMethod = typeof(TomlSerializationMethods).GetMethod(nameof(StringKeyedDictionaryDeserializerFor), BindingFlags.Static | BindingFlags.NonPublic)!;
private static MethodInfo _primitiveKeyedDictionaryMethod = typeof(TomlSerializationMethods).GetMethod(nameof(PrimitiveKeyedDictionaryDeserializerFor), BindingFlags.Static | BindingFlags.NonPublic)!;
private static MethodInfo _genericDictionarySerializerMethod = typeof(TomlSerializationMethods).GetMethod(nameof(GenericDictionarySerializer), BindingFlags.Static | BindingFlags.NonPublic)!;
private static MethodInfo _genericNullableSerializerMethod = typeof(TomlSerializationMethods).GetMethod(nameof(GenericNullableSerializer), BindingFlags.Static | BindingFlags.NonPublic)!;

Expand Down Expand Up @@ -195,6 +198,12 @@ internal static Deserialize<object> GetDeserializer(Type t, TomlSerializerOption
{
return (Deserialize<object>)_stringKeyedDictionaryMethod.MakeGenericMethod(genericArgs[1]).Invoke(null, new object[]{options})!;
}

if (genericArgs[0].IsIntegerType() || genericArgs[0] == typeof(bool) || genericArgs[0] == typeof(char))
{
// float primitives not supported due to decimal point causing issues
return (Deserialize<object>)_primitiveKeyedDictionaryMethod.MakeGenericMethod(genericArgs).Invoke(null, new object[]{options})!;
}
}

return TomlCompositeDeserializer.For(t, options);
Expand Down Expand Up @@ -279,7 +288,24 @@ private static Deserialize<Dictionary<string, T>> StringKeyedDictionaryDeseriali
return table.Entries.ToDictionary(entry => entry.Key, entry => (T)deserializer(entry.Value));
};
}


// unmanaged + IConvertible is the closest I can get to expressing "primitives only"
private static Deserialize<Dictionary<TKey, TValue>> PrimitiveKeyedDictionaryDeserializerFor<TKey, TValue>(TomlSerializerOptions options) where TKey : unmanaged, IConvertible
{
var valueDeserializer = GetDeserializer(typeof(TValue), options);

return value =>
{
if (value is not TomlTable table)
throw new TomlTypeMismatchException(typeof(TomlTable), value.GetType(), typeof(Dictionary<TKey, TValue>));
return table.Entries.ToDictionary(
entry => (TKey)(entry.Key as IConvertible).ToType(typeof(TKey), CultureInfo.InvariantCulture),
entry => (TValue)valueDeserializer(entry.Value)
);
};
}

private static TomlValue? GenericNullableSerializer<T>(T? nullable, TomlSerializerOptions options) where T : struct
{
var elementSerializer = GetSerializer(typeof(T), options);
Expand Down

0 comments on commit 4393f63

Please sign in to comment.