Skip to content

Commit 3ee7d00

Browse files
committed
Provide default deserialization types for interfaces.
ICollection<T> => List<T> IList<T> => List<T> IDictionary<K,V> => Dictionary<K,V> This resolves issue #120.
1 parent b9b8465 commit 3ee7d00

File tree

3 files changed

+106
-0
lines changed

3 files changed

+106
-0
lines changed

Assets/FullSerializer/Source/fsSerializer.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,35 @@ public void Clear() {
274274
private readonly fsCyclicReferenceManager _references;
275275
private readonly fsLazyCycleDefinitionWriter _lazyReferenceWriter;
276276

277+
/// <summary>
278+
/// Allow the user to provide default storage types for interfaces and abstract
279+
/// classes. For example, a model could have IList{int} as a parameter, but the
280+
/// serialization data does not specify a List{int} type. A IList{} -> List{}
281+
/// remapping will cause List{} to be used as the default storage type. see
282+
/// https://github.com/jacobdufault/fullserializer/issues/120 for additional
283+
/// context.
284+
/// </summary>
285+
private readonly Dictionary<Type, Type> _abstractTypeRemap;
286+
287+
private void RemapAbstractStorageTypeToDefaultType(ref Type storageType) {
288+
if ((storageType.IsInterface || storageType.IsAbstract) == false)
289+
return;
290+
291+
if (storageType.IsGenericType) {
292+
Type remappedGenericType;
293+
if (_abstractTypeRemap.TryGetValue(storageType.GetGenericTypeDefinition(), out remappedGenericType)) {
294+
Type[] genericArguments = storageType.GetGenericArguments();
295+
storageType = remappedGenericType.MakeGenericType(genericArguments);
296+
}
297+
}
298+
299+
else {
300+
Type remappedType;
301+
if (_abstractTypeRemap.TryGetValue(storageType, out remappedType))
302+
storageType = remappedType;
303+
}
304+
}
305+
277306
public fsSerializer() {
278307
_cachedConverterTypeInstances = new Dictionary<Type, fsBaseConverter>();
279308
_cachedConverters = new Dictionary<Type, fsBaseConverter>();
@@ -309,6 +338,11 @@ public fsSerializer() {
309338
_processors.Add(new fsSerializationCallbackReceiverProcessor());
310339
#endif
311340

341+
_abstractTypeRemap = new Dictionary<Type, Type>();
342+
SetDefaultStorageType(typeof(ICollection<>), typeof(List<>));
343+
SetDefaultStorageType(typeof(IList<>), typeof(List<>));
344+
SetDefaultStorageType(typeof(IDictionary<,>), typeof(Dictionary<,>));
345+
312346
Context = new fsContext();
313347
Config = new fsConfig();
314348

@@ -365,6 +399,18 @@ public void RemoveProcessor<TProcessor>() {
365399
_cachedProcessors = new Dictionary<Type, List<fsObjectProcessor>>();
366400
}
367401

402+
/// <summary>
403+
/// Provide a default storage type for the given abstract or interface type. If
404+
/// a type is deserialized which contains an interface/abstract field type and a
405+
/// mapping is provided, the mapped type will be used by default. For example,
406+
/// IList{T} => List{T} or IDictionary{TKey, TValue} => Dictionary{TKey, TValue}.
407+
/// </summary>
408+
public void SetDefaultStorageType(Type abstractType, Type defaultStorageType) {
409+
if ((abstractType.IsInterface || abstractType.IsAbstract) == false)
410+
throw new ArgumentException("|abstractType| must be an interface or abstract type");
411+
_abstractTypeRemap[abstractType] = defaultStorageType;
412+
}
413+
368414
/// <summary>
369415
/// Fetches all of the processors for the given type.
370416
/// </summary>
@@ -824,6 +870,7 @@ private fsResult InternalDeserialize_3_Inheritance(Type overrideConverterType, f
824870
objectType = type;
825871
} while (false);
826872
}
873+
RemapAbstractStorageTypeToDefaultType(ref objectType);
827874

828875
// We wait until here to actually Invoke_OnBeforeDeserialize because
829876
// we do not have the correct set of processors to invoke until
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using System.Collections.Generic;
2+
using NUnit.Framework;
3+
4+
namespace FullSerializer.Tests {
5+
public class AbstractTypeRemappingTests {
6+
public string Serialize<T>(T value) {
7+
fsData data;
8+
(new fsSerializer()).TrySerialize(value, out data).AssertSuccessWithoutWarnings();
9+
return fsJsonPrinter.PrettyJson(data);
10+
}
11+
12+
public T Deserialize<T>(string content) {
13+
fsData data;
14+
fsJsonParser.Parse(content, out data).AssertSuccessWithoutWarnings();
15+
var result = default(T);
16+
(new fsSerializer()).TryDeserialize(data, ref result).AssertSuccessWithoutWarnings();
17+
return result;
18+
}
19+
20+
[Test]
21+
public void IListDeserializedAsList() {
22+
var value = new List<int> { 1, 2, 3 };
23+
string serialized = Serialize<List<int>>(value);
24+
IList<int> deserialized = Deserialize<IList<int>>(serialized);
25+
Assert.IsInstanceOf<List<int>>(deserialized);
26+
CollectionAssert.AreEquivalent(value, deserialized);
27+
}
28+
29+
[Test]
30+
public void ICollectionDeserializedAsList() {
31+
var value = new List<int> { 1, 2, 3 };
32+
string serialized = Serialize<List<int>>(value);
33+
ICollection<int> deserialized = Deserialize<ICollection<int>>(serialized);
34+
Assert.IsInstanceOf<List<int>>(deserialized);
35+
CollectionAssert.AreEquivalent(value, deserialized);
36+
}
37+
38+
[Test]
39+
public void IDictionaryDeserializedAsDictionary() {
40+
var value = new Dictionary<string, int> { { "1", 1 }, { "2", 2 }, { "3", 3 } };
41+
string serialized = Serialize<Dictionary<string, int>>(value);
42+
IDictionary<string, int> deserialized = Deserialize<IDictionary<string, int>>(serialized);
43+
Assert.IsInstanceOf<Dictionary<string, int>>(deserialized);
44+
CollectionAssert.AreEquivalent(value, deserialized);
45+
}
46+
}
47+
}

Assets/FullSerializer/Testing/Editor/AbstractTypeRemappingTests.cs.meta

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)