@@ -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
0 commit comments