Skip to content

Commit

Permalink
Merge pull request #1187 from Alezy80/more-collections-serializer
Browse files Browse the repository at this point in the history
Enable serialization IEnumerable<T> and IReadOnlyDictionary<T> descendants that have constructor with relevant collection
  • Loading branch information
AArnott committed May 17, 2021
2 parents 7af6eea + f5e5bf9 commit a77187e
Show file tree
Hide file tree
Showing 8 changed files with 292 additions and 3 deletions.
Expand Up @@ -464,6 +464,25 @@ protected override void Add(TCollection collection, int index, TElement value, M
}
}

public sealed class GenericEnumerableFormatter<TElement, TCollection> : CollectionFormatterBase<TElement, TElement[], TCollection>
where TCollection : IEnumerable<TElement>
{
protected override TElement[] Create(int count, MessagePackSerializerOptions options)
{
return new TElement[count];
}

protected override void Add(TElement[] collection, int index, TElement value, MessagePackSerializerOptions options)
{
collection[index] = value;
}

protected override TCollection Complete(TElement[] intermediateCollection)
{
return (TCollection)Activator.CreateInstance(typeof(TCollection), intermediateCollection);
}
}

public sealed class LinkedListFormatter<T> : CollectionFormatterBase<T, LinkedList<T>, LinkedList<T>.Enumerator, LinkedList<T>>
{
protected override void Add(LinkedList<T> collection, int index, T value, MessagePackSerializerOptions options)
Expand Down
Expand Up @@ -174,6 +174,25 @@ protected override TDictionary Create(int count, MessagePackSerializerOptions op
}
}

public sealed class GenericReadOnlyDictionaryFormatter<TKey, TValue, TDictionary> : DictionaryFormatterBase<TKey, TValue, Dictionary<TKey, TValue>, TDictionary>
where TDictionary : IReadOnlyDictionary<TKey, TValue>
{
protected override void Add(Dictionary<TKey, TValue> collection, int index, TKey key, TValue value, MessagePackSerializerOptions options)
{
collection.Add(key, value);
}

protected override Dictionary<TKey, TValue> Create(int count, MessagePackSerializerOptions options)
{
return new Dictionary<TKey, TValue>(count, options.Security.GetEqualityComparer<TKey>());
}

protected override TDictionary Complete(Dictionary<TKey, TValue> intermediateCollection)
{
return (TDictionary)Activator.CreateInstance(typeof(TDictionary), intermediateCollection);
}
}

public sealed class InterfaceDictionaryFormatter<TKey, TValue> : DictionaryFormatterBase<TKey, TValue, Dictionary<TKey, TValue>, IDictionary<TKey, TValue>>
{
protected override void Add(Dictionary<TKey, TValue> collection, int index, TKey key, TValue value, MessagePackSerializerOptions options)
Expand Down
Expand Up @@ -316,13 +316,54 @@ internal static object GetFormatter(Type t)
return CreateInstance(typeof(GenericDictionaryFormatter<,,>), new[] { keyType, valueType, t });
}

// generic dictionary with collection ctor
var dictionaryInterfaceDef = ti.ImplementedInterfaces.FirstOrDefault(x => x.GetTypeInfo().IsConstructedGenericType() &&
(x.GetGenericTypeDefinition() == typeof(IDictionary<,>) || x.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<,>)));
if (dictionaryInterfaceDef != null)
{
Type keyType = dictionaryInterfaceDef.GenericTypeArguments[0];
Type valueType = dictionaryInterfaceDef.GenericTypeArguments[1];
Type[] allowedParameterTypes = new Type[]
{
typeof(IDictionary<,>).MakeGenericType(keyType, valueType),
typeof(IReadOnlyDictionary<,>).MakeGenericType(keyType, valueType),
typeof(IEnumerable<>).MakeGenericType(typeof(KeyValuePair<,>).MakeGenericType(keyType, valueType)),
};
foreach (var constructor in ti.DeclaredConstructors)
{
ParameterInfo[] parameters = constructor.GetParameters();
if (parameters.Length == 1 &&
allowedParameterTypes.Any(allowedType => parameters[0].ParameterType.IsAssignableFrom(allowedType)))
{
return CreateInstance(typeof(GenericReadOnlyDictionaryFormatter<,,>), new[] { keyType, valueType, t });
}
}
}

// generic collection
var collectionDef = ti.ImplementedInterfaces.FirstOrDefault(x => x.GetTypeInfo().IsConstructedGenericType() && x.GetGenericTypeDefinition() == typeof(ICollection<>));
if (collectionDef != null && ti.DeclaredConstructors.Any(x => x.GetParameters().Length == 0))
{
Type elemType = collectionDef.GenericTypeArguments[0];
return CreateInstance(typeof(GenericCollectionFormatter<,>), new[] { elemType, t });
}

// generic IEnumerable collection
// looking for combination of IEnumerable<T> and constructor that takes
// enumeration of the same type
foreach (var enumerableCollectionDef in ti.ImplementedInterfaces.Where(x => x.GetTypeInfo().IsConstructedGenericType() && x.GetGenericTypeDefinition() == typeof(IEnumerable<>)))
{
Type elemType = enumerableCollectionDef.GenericTypeArguments[0];
Type paramInterface = typeof(IEnumerable<>).MakeGenericType(elemType);
foreach (var constructor in ti.DeclaredConstructors)
{
var parameters = constructor.GetParameters();
if (parameters.Length == 1 && parameters[0].ParameterType.IsAssignableFrom(paramInterface))
{
return CreateInstance(typeof(GenericEnumerableFormatter<,>), new[] { elemType, t });
}
}
}
}

return null;
Expand Down
Expand Up @@ -251,6 +251,41 @@ public void CustomGenericInterfaceCollectionTest()
}
}

[Fact]
public void CustomGenericEnumerableCollectionTest()
{
{
var xs = new ImplGenericEnumerable();
xs.Add(1);
xs.Add(2);
xs.Add(3);

this.Convert(xs).Is(1, 2, 3);
}
}

[Fact]
public void CustomGenericInterfaceReadOnlyCollectionTest()
{
{
var xs = new ImplGenericReadonlyCollection();
xs.Add(1);
xs.Add(2);
xs.Add(3);

this.Convert(xs).Is(1, 2, 3);
}

{
var xs = new ImplGenericGenericReadOnlyCollection<int>();
xs.Add(1);
xs.Add(2);
xs.Add(3);

this.Convert(xs).Is(1, 2, 3);
}
}

[Fact]
public void CustomGenericDictionaryTest()
{
Expand All @@ -262,6 +297,18 @@ public void CustomGenericDictionaryTest()
d2["foo"].Is(10);
d2["bar"].Is(20);
}

[Fact]
public void CustomGenericReadOnlyDictionaryTest()
{
var d = new ImplReadonlyDictionary();
d.Add("foo", 10);
d.Add("bar", 20);

var d2 = this.Convert(d);
d2["foo"].Is(10);
d2["bar"].Is(20);
}
}

public class ImplNonGenericList : IList
Expand Down Expand Up @@ -429,6 +476,38 @@ IEnumerator IEnumerable.GetEnumerator()
}
}

public class ImplGenericGenericReadOnlyCollection<T> : IReadOnlyCollection<T>
{
private readonly ICollection<T> inner;

public ImplGenericGenericReadOnlyCollection()
{
this.inner = new List<T>();
}

public ImplGenericGenericReadOnlyCollection(IEnumerable<T> items)
{
this.inner = new List<T>(items);
}

public IEnumerator<T> GetEnumerator()
{
return inner.GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)inner).GetEnumerator();
}

public int Count => inner.Count;

public void Add(T item)
{
inner.Add(item);
}
}

public class ImplDictionary : IDictionary<string, int>
{
private readonly Dictionary<string, int> inner;
Expand Down Expand Up @@ -503,4 +582,119 @@ IEnumerator IEnumerable.GetEnumerator()
return ((IDictionary<string, int>)inner).GetEnumerator();
}
}

public class ImplGenericReadonlyCollection : IReadOnlyCollection<int>
{
private readonly ICollection<int> inner;

public ImplGenericReadonlyCollection()
{
this.inner = new List<int>();
}

public ImplGenericReadonlyCollection(IEnumerable<int> items)
{
this.inner = new List<int>(items);
}

public int Count => inner.Count;

public IEnumerator<int> GetEnumerator()
{
return inner.GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator()
{
return inner.GetEnumerator();
}

public void Add(int item)
{
inner.Add(item);
}
}

public class ImplReadonlyDictionary : IReadOnlyDictionary<string, int>
{
private readonly Dictionary<string, int> inner;

public ImplReadonlyDictionary()
{
this.inner = new Dictionary<string, int>();
}

public ImplReadonlyDictionary(IDictionary<string, int> inner)
{
this.inner = new Dictionary<string, int>(inner);
}

public IEnumerator<KeyValuePair<string, int>> GetEnumerator()
{
return inner.GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)inner).GetEnumerator();
}

public int Count => inner.Count;

public bool ContainsKey(string key)
{
return inner.ContainsKey(key);
}

public bool TryGetValue(string key, out int value)
{
return inner.TryGetValue(key, out value);
}

public int this[string key] => inner[key];

public IEnumerable<string> Keys => inner.Keys;

public IEnumerable<int> Values => inner.Values;

public void Add(string key, int value)
{
inner.Add(key, value);
}
}

public class ImplGenericEnumerable : IEnumerable<int>
{
private readonly List<int> inner = new List<int>();

public ImplGenericEnumerable()
{
}

public ImplGenericEnumerable(IEnumerable<double> values)
{
}

public ImplGenericEnumerable(IEnumerable<int> values)
{
inner.AddRange(values);
}

public ImplGenericEnumerable(ICollection<int> values)
{
inner.AddRange(values);
}

public IEnumerator<int> GetEnumerator()
{
return inner.GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)inner).GetEnumerator();
}

public void Add(int item) => inner.Add(item);
}
}
4 changes: 4 additions & 0 deletions src/MessagePack/net5.0/PublicAPI.Unshipped.txt
Expand Up @@ -22,3 +22,7 @@ static MessagePack.Nil.operator !=(MessagePack.Nil left, MessagePack.Nil right)
static MessagePack.Nil.operator ==(MessagePack.Nil left, MessagePack.Nil right) -> bool
static readonly MessagePack.Formatters.HalfFormatter.Instance -> MessagePack.Formatters.IMessagePackFormatter<System.Half>
virtual MessagePack.MessagePackStreamReader.Dispose(bool disposing) -> void
MessagePack.Formatters.GenericEnumerableFormatter<TElement, TCollection>
MessagePack.Formatters.GenericEnumerableFormatter<TElement, TCollection>.GenericEnumerableFormatter() -> void
MessagePack.Formatters.GenericReadOnlyDictionaryFormatter<TKey, TValue, TDictionary>
MessagePack.Formatters.GenericReadOnlyDictionaryFormatter<TKey, TValue, TDictionary>.GenericReadOnlyDictionaryFormatter() -> void
6 changes: 5 additions & 1 deletion src/MessagePack/netcoreapp2.1/PublicAPI.Unshipped.txt
Expand Up @@ -13,4 +13,8 @@ MessagePack.SequencePool.SequencePool(int maxSize, System.Buffers.ArrayPool<byte
MessagePack.TinyJsonException.TinyJsonException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) -> void
static MessagePack.Nil.operator !=(MessagePack.Nil left, MessagePack.Nil right) -> bool
static MessagePack.Nil.operator ==(MessagePack.Nil left, MessagePack.Nil right) -> bool
virtual MessagePack.MessagePackStreamReader.Dispose(bool disposing) -> void
virtual MessagePack.MessagePackStreamReader.Dispose(bool disposing) -> void
MessagePack.Formatters.GenericEnumerableFormatter<TElement, TCollection>
MessagePack.Formatters.GenericEnumerableFormatter<TElement, TCollection>.GenericEnumerableFormatter() -> void
MessagePack.Formatters.GenericReadOnlyDictionaryFormatter<TKey, TValue, TDictionary>
MessagePack.Formatters.GenericReadOnlyDictionaryFormatter<TKey, TValue, TDictionary>.GenericReadOnlyDictionaryFormatter() -> void
6 changes: 5 additions & 1 deletion src/MessagePack/netcoreapp3.1/PublicAPI.Unshipped.txt
Expand Up @@ -15,4 +15,8 @@ MessagePack.SequencePool.SequencePool(int maxSize, System.Buffers.ArrayPool<byte
MessagePack.TinyJsonException.TinyJsonException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) -> void
static MessagePack.Nil.operator !=(MessagePack.Nil left, MessagePack.Nil right) -> bool
static MessagePack.Nil.operator ==(MessagePack.Nil left, MessagePack.Nil right) -> bool
virtual MessagePack.MessagePackStreamReader.Dispose(bool disposing) -> void
virtual MessagePack.MessagePackStreamReader.Dispose(bool disposing) -> void
MessagePack.Formatters.GenericEnumerableFormatter<TElement, TCollection>
MessagePack.Formatters.GenericEnumerableFormatter<TElement, TCollection>.GenericEnumerableFormatter() -> void
MessagePack.Formatters.GenericReadOnlyDictionaryFormatter<TKey, TValue, TDictionary>
MessagePack.Formatters.GenericReadOnlyDictionaryFormatter<TKey, TValue, TDictionary>.GenericReadOnlyDictionaryFormatter() -> void
6 changes: 5 additions & 1 deletion src/MessagePack/netstandard2.0/PublicAPI.Unshipped.txt
Expand Up @@ -15,4 +15,8 @@ MessagePack.SequencePool.SequencePool(int maxSize, System.Buffers.ArrayPool<byte
MessagePack.TinyJsonException.TinyJsonException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) -> void
static MessagePack.Nil.operator !=(MessagePack.Nil left, MessagePack.Nil right) -> bool
static MessagePack.Nil.operator ==(MessagePack.Nil left, MessagePack.Nil right) -> bool
virtual MessagePack.MessagePackStreamReader.Dispose(bool disposing) -> void
virtual MessagePack.MessagePackStreamReader.Dispose(bool disposing) -> void
MessagePack.Formatters.GenericEnumerableFormatter<TElement, TCollection>
MessagePack.Formatters.GenericEnumerableFormatter<TElement, TCollection>.GenericEnumerableFormatter() -> void
MessagePack.Formatters.GenericReadOnlyDictionaryFormatter<TKey, TValue, TDictionary>
MessagePack.Formatters.GenericReadOnlyDictionaryFormatter<TKey, TValue, TDictionary>.GenericReadOnlyDictionaryFormatter() -> void

0 comments on commit a77187e

Please sign in to comment.