Filters

A filter is a function that takes a Type as a parameter and tells UniversalSerializer how to serialize it.

Contrary to the containers, a filter does not replace a value. It only gives some information to UniversalSerializer about this type.

IMPORTANT:

As of 2018-03, I suggest you to implement containers rather than filters for third-party types.
The reason is one can not rely on private members and constructors of types in a DLL he does not control.
Who knows if someday the third-party DLL author will not modify these private data ?
Even if you adapt your filter accordingly, previous serialized files may not be readable anymore.
A container can be adapted too. But as it defines its own members, old serialized files remain readable.

The reason for this notice is I had bad experience with some framework types that have different implementations. For example the ordinary type System.Nullable<T> has private fields with different names under .NET, .NET Core and Silverlight, and even between .NET 3.5 and .NET 4.0. I had to create a container to manage this type independently to the frameworks.

Please note filters may be deprecated in the next main API (and file) version of UniversalSerializer.
Or maybe limited to public information such as the parametric constructors.


Available filter categories

Type validator filter

This filter allows you to prevent UniversalSerializer from serializing some problematic types.

For example, I met some classes using System.IntPtr. Serializing this type only leads to problems since they are only used internally by the classes, even when they are stored in public properties/fields.

Private fields adder filter

This filter tells the serializer to add a particular private field to the serialization data.

For example, System.Windows.Controls.Panel needs _uiElementCollection to fill its property Children, since Children is read-only. With the filter, the solution is easy. And any type that inherits Panel, such as StackPanel, will benefit this filter.

ForcedParametricConstructorTypes

It is not a filter but a list of types. When a type is in this list, UniversalSerializer ignores its default (not parametric) constructor and searches for a parametric constructor.

Example: System.Windows.Forms.PropertyManager. It is much easier to use its parametric constructor than to write a ITypeContainer for this type.

CanTestDefaultConstructor

UniversalSerializer usually tries to build one instance per type using its default constructor (when available). The problem is some types should not be constructed outside deserialization, for example when their constructor increments a static counter.

This filter prevents UniversalSerializer from testing this construction.

DefaultConstructorTestCleaner

UniversalSerializer usually tries to build one instance per type using its default constructor (when available). Some types, as the WPF's System.Windows.Window, needs a cleaner to be called before instance destruction.
In the example of WPF Window, we have to call Window.Close(), otherwise the application will not be closed correctly (it will wait for all WPF windows to be closed).

Making a set of filters

Please note the filter mechanism is totally independent from the containers (ITypeContainer). They can be used together, or separately.
When a type has a container, its serialization will not be affected by any filter (they are ignored).

Let's take an example:

public class ThisClassNeedsFilters
{
	public ShouldNotBeSerialized Useless;
	private int Integer;
	public string Value { get { return this.Integer.ToString(); } }

	public ThisClassNeedsFilters()
	{
	}

	public ThisClassNeedsFilters(int a)
	{
		this.Integer = a;
		this.Useless = new ShouldNotBeSerialized();
	}
}

public class ShouldNotBeSerialized
{
}

This class (ThisClassNeedsFilters) have some problems:

  • It contains a ShouldNotBeSerialized. Let's imagine the class ShouldNotBeSerialized has to be avoided for some reasons, I don't know why, maybe it is poisoned!
  • The field Integer is not public and therefore is ignored by the serializer(s).
  • Even the constructor parameter name is different from any field or property. Anyway the serializer does not need this constructor, as it already has a default constructor.

To overcome these problems, we write a custom set of filters:

/// <summary>
/// Tells the serializer to add some certain private fields to store the type.
/// </summary>
static FieldInfo[] MyAdditionalPrivateFieldsAdder(Type t)
{
	if (Tools.TypeIs(t, typeof(ThisClassNeedsFilters)))
		return new FieldInfo[] { Tools.FieldInfoFromName(t, "Integer") };
	return null;
}
/// <summary>
/// Returns 'false' if this type should not be serialized at all.
/// That will let the default value created by the constructor of its container class/structure.
/// </summary>
static bool MyTypeSerializationValidator(Type t)
{
	return ! Tools.TypeIs(t, typeof(ShouldNotBeSerialized));
}

They are self-explanatory:

  • FieldInfo[] MyAdditionalPrivateFieldsAdder(Type t)
    
    makes the serializer add a private field (Integer) to every source instance of this type (ThisClassNeedsFilters).
  • bool MyTypeSerializationValidator(Type t)
    
    It prevents the serializer from storing any instance of this type (ShouldNotBeSerialized). Consequently, any instance of ThisClassNeedsFilters will not set the Useless field.

We declare the CustomModifiers :

public class CustomFiltersTestModifier : CustomModifiers
{
public CustomFiltersTestModifier()
	: base(FilterSets : new FilterSet[] {
		new FilterSet() { 
			AdditionalPrivateFieldsAdder=MyAdditionalPrivateFieldsAdder, 
			TypeSerializationValidator=MyTypeSerializationValidator } })
	{        
	}
}

This declaration will automatically be found by the deserializer.

Now we serialize it:

/* This example needs custom filters.
Normally, this class can be serialized but with wrong fields.
Thanks to these filters, we can serialize the class appropriately.
 */

using (MemoryStream ms = new MemoryStream())
{
	var p = new Parameters() { Stream = ms };
	var ser = new UniversalSerializer(p);

	var data = new ThisClassNeedsFilters(123);
	ser.Serialize(data);
	var data2 = ser.Deserialize<ThisClassNeedsFilters>();

	bool ok = data2.Value == "123" && data2.Useless == null;
}

Tool help functions

The static class Tools offers some help:

  • Type Tools.TypeIs(Type ObjectType, Type SearchedType)
    
    It is similar to the C#'s 'is', but for Types.
    For example, TypeIs((typeof(List<int>), typeof(List<>)) returns true.
  • Type Tools.DerivedType(Type ObjectType, Type SearchedType)
    

    Returns the type corresponding to SearchedType that is inherited by ObjectType.

    For example, DerivedType(typeof(MyList), typeof(List<>)) returns typeof(List<int>) when MyList is
    class MyList: List<int> { }

  • FieldInfo Tools.FieldInfoFromName(Type t, string name)
    
    Returns the FieldInfo of the named field of the type.