Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
### 0.9.12 January 20 2020 ####
### 0.9.13 February 09 2020 ####

* Added version tolerance during deserialization.
* Added `ArrayList` and non-generic `IEnumerable` support.
* [Added support for serializing and deserializing `IDictionary<TKey, TValue>`](https://github.com/akkadotnet/Hyperion/pull/156)
135 changes: 135 additions & 0 deletions src/Hyperion.Tests/GenericDictionarySerializerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
using System;
using System.Collections;
using System.Collections.Generic;
using FluentAssertions;
using Xunit;

namespace Hyperion.Tests
{
public class GenericDictionarySerializerTests : TestBase
{
[Fact]
public void CanSerializeDictionary()
{
var customDict = new CustomDictionary<string, int>(new Dictionary<string, int>()
{
["key"] = 1
});
SerializeAndAssertEquivalent(customDict);
}

private void SerializeAndAssertEquivalent<T>(T expected)
{
Serialize(expected);
Reset();
var res = Deserialize<T>();
res.Should().BeEquivalentTo(expected);
AssertMemoryStreamConsumed();
}

/// <summary>
/// Just a custom class wrapper for another <see cref="IDictionary{TKey,TValue}"/>
/// </summary>
class CustomDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
private readonly IDictionary<TKey, TValue> _dictGeneric;

/// <summary>
/// For serialization
/// </summary>
public CustomDictionary() : this(new Dictionary<TKey, TValue>())
{
}

public CustomDictionary(Dictionary<TKey, TValue> dict)
{
_dictGeneric = dict;
}

/// <inheritdoc />
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return _dictGeneric.GetEnumerator();
}

/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable) _dictGeneric).GetEnumerator();
}

/// <inheritdoc />
public void Add(KeyValuePair<TKey, TValue> item)
{
_dictGeneric.Add(item);
}

/// <inheritdoc />
public void Clear()
{
_dictGeneric.Clear();
}

/// <inheritdoc />
public bool Contains(KeyValuePair<TKey, TValue> item)
{
return _dictGeneric.Contains(item);
}

/// <inheritdoc />
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
_dictGeneric.CopyTo(array, arrayIndex);
}

/// <inheritdoc />
public bool Remove(KeyValuePair<TKey, TValue> item)
{
return _dictGeneric.Remove(item);
}

/// <inheritdoc />
public int Count => _dictGeneric.Count;

/// <inheritdoc />
public bool IsReadOnly => _dictGeneric.IsReadOnly;

/// <inheritdoc />
public void Add(TKey key, TValue value)
{
_dictGeneric.Add(key, value);
}

/// <inheritdoc />
public bool ContainsKey(TKey key)
{
return _dictGeneric.ContainsKey(key);
}

/// <inheritdoc />
public bool Remove(TKey key)
{
return _dictGeneric.Remove(key);
}

/// <inheritdoc />
public bool TryGetValue(TKey key, out TValue value)
{
return _dictGeneric.TryGetValue(key, out value);
}

/// <inheritdoc />
public TValue this[TKey key]
{
get => _dictGeneric[key];
set => _dictGeneric[key] = value;
}

/// <inheritdoc />
public ICollection<TKey> Keys => _dictGeneric.Keys;

/// <inheritdoc />
public ICollection<TValue> Values => _dictGeneric.Values;
}
}
}
55 changes: 41 additions & 14 deletions src/Hyperion/SerializerFactories/DictionarySerializerFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,26 +39,31 @@ public override ValueSerializer BuildSerializer(Serializer serializer, Type type
var preserveObjectReferences = serializer.Options.PreserveObjectReferences;
var ser = new ObjectSerializer(type);
typeMapping.TryAdd(type, ser);
var elementSerializer = serializer.GetSerializerByType(typeof (DictionaryEntry));
var dictionaryTypes = GetKeyValuePairType(type);
var elementSerializer = serializer.GetSerializerByType(dictionaryTypes.KeyValuePairType);

ObjectReader reader = (stream, session) =>
{
throw new NotSupportedException("Generic IDictionary<TKey,TValue> are not yet supported");
#pragma warning disable CS0162 // Unreachable code detected
var instance = Activator.CreateInstance(type);
#pragma warning restore CS0162 // Unreachable code detected
var instance = Activator.CreateInstance(type); // IDictionary<TKey, TValue>
if (preserveObjectReferences)
{
session.TrackDeserializedObject(instance);
}
var count = stream.ReadInt32(session);
var entries = new DictionaryEntry[count];
for (var i = 0; i < count; i++)
{
var entry = (DictionaryEntry) stream.ReadObject(session);
entries[i] = entry;
var entry = stream.ReadObject(session); // KeyValuePair<TKey, TValue>

// Get entry.Key and entry.Value
var key = dictionaryTypes.KeyValuePairType.GetProperty(nameof(KeyValuePair<object, object>.Key)).GetValue(entry, null);
var value = dictionaryTypes.KeyValuePairType.GetProperty(nameof(KeyValuePair<object, object>.Value)).GetValue(entry, null);

// Same as: instance.Add(key, value)
dictionaryTypes.DictionaryInterfaceType
.GetMethod(nameof(IDictionary<object, object>.Add), new []{ dictionaryTypes.KeyType, dictionaryTypes.ValueType })
.Invoke(instance, new [] { key, value });
}
//TODO: populate dictionary

return instance;
};

Expand All @@ -68,19 +73,41 @@ public override ValueSerializer BuildSerializer(Serializer serializer, Type type
{
session.TrackSerializedObject(obj);
}
var dict = obj as IDictionary;

var dict = obj as IEnumerable; // IDictionary<T, V> is IEnumerable<KeyValuePair<T, V>>
var count = dict.Cast<object>().Count();
// ReSharper disable once PossibleNullReferenceException
Int32Serializer.WriteValueImpl(stream,dict.Count,session);
Int32Serializer.WriteValueImpl(stream, count, session);
foreach (var item in dict)
{
stream.WriteObject(item, typeof (DictionaryEntry), elementSerializer,
serializer.Options.PreserveObjectReferences, session);
// elementSerializer.WriteValue(stream,item,session);
stream.WriteObject(item, dictionaryTypes.KeyValuePairType, elementSerializer, serializer.Options.PreserveObjectReferences, session);
}
};
ser.Initialize(reader, writer);

return ser;
}

private GenericDictionaryTypes GetKeyValuePairType(Type dictImplementationType)
{
var dictInterface = dictImplementationType.GetInterfaces().First(i => i.GetGenericTypeDefinition() == typeof (IDictionary<,>));
var keyType = dictInterface.GetGenericArguments()[0];
var valueType = dictInterface.GetGenericArguments()[1];
return new GenericDictionaryTypes()
{
KeyType = keyType,
ValueType = valueType,
KeyValuePairType = typeof(KeyValuePair<,>).MakeGenericType(keyType, valueType),
DictionaryInterfaceType = typeof(IDictionary<,>).MakeGenericType(keyType, valueType)
};
}

class GenericDictionaryTypes
{
public Type KeyType { get; set; }
public Type ValueType { get; set; }
public Type KeyValuePairType { get; set; }
public Type DictionaryInterfaceType { get; set; }
}
}
}
7 changes: 3 additions & 4 deletions src/common.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,16 @@
<PropertyGroup>
<Copyright>Copyright © 2016-2017 Akka.NET Team</Copyright>
<Authors>Akka.NET Team</Authors>
<VersionPrefix>0.9.12</VersionPrefix>
<PackageReleaseNotes>Added version tolerance during deserialization.
Added `ArrayList` and non-generic `IEnumerable` support.</PackageReleaseNotes>
<VersionPrefix>0.9.13</VersionPrefix>
<PackageReleaseNotes>[Added support for serializing and deserializing `IDictionary&lt;TKey, TValue&gt;`](https://github.com/akkadotnet/Hyperion/pull/156)</PackageReleaseNotes>
<PackageIconUrl>http://getakka.net/images/akkalogo.png</PackageIconUrl>
<PackageProjectUrl>https://github.com/akkadotnet/Hyperion</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/akkadotnet/Hyperion/blob/master/LICENSE</PackageLicenseUrl>
<NoWarn>$(NoWarn);CS1591</NoWarn>
</PropertyGroup>
<PropertyGroup>
<XunitVersion>2.4.1</XunitVersion>
<TestSdkVersion>16.4.0</TestSdkVersion>
<TestSdkVersion>16.5.0</TestSdkVersion>
<NBenchVersion>1.2.2</NBenchVersion>
</PropertyGroup>
</Project>