Click here to Skip to main content
15,886,799 members
Articles / Programming Languages / C#

UniversalSerializer

Rate me:
Please Sign up or sign in to vote.
4.97/5 (108 votes)
15 Apr 2018Ms-RL31 min read 258.1K   4K   299  
An universal and easy serialization library for .NET and .NET Core.

// Copyright Christophe Bertrand.

/* Reminder:
 * KwownTypes: lets the programmer user have type descriptors in common.
 * SerializationTypeDescriptors: type descriptors in a small form, for serialization.
 */

using System;
using System.Collections;
using System.IO;
#if DEBUG
using System.Diagnostics;
#endif
using UniversalSerializerLib3.TypeManagement;
using System.Collections.Generic;
using UniversalSerializerLib3.StreamFormat3;

namespace UniversalSerializerLib3
{

	// ######################################################################


	// ######################################################################

	public abstract partial class DeserializationFormatter
	{
		// -------------------------------------------------------------------------------

		/// <summary>
		/// Serialize (another) data to the stream(s), using the same CustomParameters, keeping types in common.
		/// If the stream position is the end of the stream, the position is set to 0 first.
		/// </summary>
		public T DeserializeAnotherData<T>()
		{
			return (T)this.DeserializeAnotherData();
		}

		// -------------------------------------------------------------------------------

		/// <summary>
		/// Serialize (another) data to the stream(s), using the same CustomParameters, keeping types in common.
		/// If the stream position is the end of the stream, the position is set to 0 first.
		/// </summary>
		public Object DeserializeAnotherData()
		{
			return this.Deserialize();
		}

		// ######################################################################

		#region Sub-Deserializer
		InstancesChannel instancesChannel;
		L3TypeManager TypeDescriptorTypeManager;
		bool DataEndMarkHasBeenPassed;
		internal L3TypeManagerCollection typeManagerCollection;

		internal Object Deserialize()
		{
			this.DataEndMarkHasBeenPassed = false;
			this.instancesChannel = new InstancesChannel();
			if (this.stream.Position == this.stream.Length)
				this.stream.Position = 0;
			this.SetStreamPosition(this.stream.Position); // Useful if any cache memory.


			this.PassPreamble();

			if (this.typeManagerCollection == null)
				this.typeManagerCollection =
					new L3TypeManagerCollection(this.parameters.customModifiers, this.Version);

			this.TypeDescriptorTypeManager = this.typeManagerCollection.GetTypeManager((int)SerializationFormatter.CompulsoryType.SerializationTypeDescriptorIndex);

			if (this.Version == StreamFormatVersion.Version3_0)
			{
				// We load the Header (new in version 3.0):

				Header header = (Header)this.LookForObjectAndManagesChannels(
					 this.typeManagerCollection.GetTypeManager((int)SerializationFormatter.CompulsoryType.HeaderIndex),
					 null, null, false);

				// now we try to load these assemblies:
				foreach (var ai in header.AssemblyIdentifiers)
				{
					ai.Load();
				}
			}

			object o = this.LookForObjectAndManagesChannels(null, null, null, false);

			if (!this.DataEndMarkHasBeenPassed)
				this.PassDataEndMark();
			this.PassTreeEndTag();

			return o;
		}

		// --------------------------------------------------

		internal class Signal
		{
		}
		static readonly Signal DeserializationTerminated = new Signal();
		internal static readonly Signal NoValue = new Signal();

		// --------------------------------------------------

		/// <summary>
		/// 
		/// </summary>
		/// <returns>Deserialized object, or ElementTypes.TypesChannelSection if some type has been described, or null if no more elements have been found.</returns>
		Object LookForObjectAndManagesChannels(
			L3TypeManager WantedType,
 object AlreadyInstanciatedCollectionOrDictionary, // For inner collection or dictionary.
long? NumberOfElements, // For inner collection or dictionary.
							bool AddToTheInstances)
		{
			object obj = this._LookForObjectAndManagesChannels(
				WantedType,
 AlreadyInstanciatedCollectionOrDictionary, NumberOfElements, AddToTheInstances);

			var obj2 = obj as ITypeContainer;
			if (obj2 != null)
			{
				var obj3 = obj2.Deserialize();
				return obj3;
			}
			return obj;
		}

		// --------------------------------------------------

		/// <summary>
		/// 
		/// </summary>
		/// <returns>Deserialized object, or ElementTypes.TypesChannelSection if some type has been described, or DeserializationTerminated if no more elements have been found.</returns>
		[System.Runtime.CompilerServices.MethodImpl((System.Runtime.CompilerServices.MethodImplOptions)256)] // AggressiveInlining
		Object _LookForObjectAndManagesChannels(
			L3TypeManager WantedType,
 object AlreadyInstanciatedCollectionOrDictionary, // For inner collection or dictionary.
long? NumberOfElements, // For inner collection or dictionary.
							bool AddToTheInstances)
		{
			Element e = new Element();

			{
				bool found =
					this.GetNextElementAs(ref e, true, null, WantedType, false, false);

				if (!found)
				{
					this.DataEndMarkHasBeenPassed = true;
					return DeserializationTerminated;
				}
			}

			switch (e.ElementType)
			{
				case ElementTypes.TypesChannelSection:
					// Deserialize a type descriptor and add it to the type manager collection:
					{
						SerializationTypeDescriptor td =
							(SerializationTypeDescriptor)this.LookForObjectAndManagesChannels(
								this.TypeDescriptorTypeManager, null, null, false);
						this.typeManagerCollection.AddNewType(td);
						this.PassClosingTag(ElementTypes.TypesChannelSection);
						return this.LookForObjectAndManagesChannels(WantedType
, AlreadyInstanciatedCollectionOrDictionary
, NumberOfElements
, false
); // look for next element.
					}

				case ElementTypes.InstancesChannelSection:
					// Deserialize an object and add it to the root of the instances channel:
					{
						object instance = this.LookForObjectAndManagesChannels(null
, null
, null
, true
);
						this.PassClosingTag(ElementTypes.InstancesChannelSection);
						object obj = this.LookForObjectAndManagesChannels(WantedType
, AlreadyInstanciatedCollectionOrDictionary
, NumberOfElements
, false
); // look for next element.

						if (obj == DeserializationTerminated)
							return instance;
						return obj;
					}

				case ElementTypes.SubBranch: // Deserializes a structured object:
					return this.DeserializeSubBranch(e, WantedType, AlreadyInstanciatedCollectionOrDictionary, AddToTheInstances);

				case ElementTypes.PrimitiveValue:
					{
						Object obj;
						bool closeTagAlreadyPassed = false;
						{
							L3TypeManager t =
							e.typeIndexIsKnown ?
							this.typeManagerCollection.GetTypeManager(e.typeIndex)
							: WantedType;

							// TODO: set GetSimpleValueInElement as generic and write a 'switch' here. Beware to avoid double switch.

							obj = this.GetSimpleValueInElement(
								t);
							if (obj == DeserializationFormatter.NoValue)
							{
								// specific to xml. In xml, we have <p></p> for a string.Empty .
#if DEBUG
								if (this.GetType() != typeof(XmlDeserializationFormatter))
									throw new Exception();
#endif
								closeTagAlreadyPassed = true;
								obj = string.Empty;
							}
						}


						if (AddToTheInstances)
						{
#if DEBUG
							if (obj.GetType() != typeof(string))
								Debugger.Break();
#endif
							this.instancesChannel.Instances.Add(obj);
						}

						if (!closeTagAlreadyPassed)
							this.PassClosingTag(ElementTypes.PrimitiveValue);

						return obj;
					}

				case ElementTypes.Reference:
					{
						Object obj;

#if DEBUG
						if (AddToTheInstances)
							Debugger.Break();
#endif
						obj = this.instancesChannel.Instances[e.InstanceIndex.Value];

						if (e.NeedsAnEndElement)
						{
#if DEBUG
							// In xml, <e/> elements can be closed in the same tag.
							if (this.GetType() == typeof(XmlDeserializationFormatter))
								throw new Exception();
#endif
							this.PassClosingTag(ElementTypes.Reference);
						}

						return obj;
					}

				case ElementTypes.Null:
					{
						Object obj;

						obj = null;

						if (e.NeedsAnEndElement)
						{
#if DEBUG
							// In xml, <e/> elements can be closed in the same tag.
							if (this.GetType() == typeof(XmlDeserializationFormatter))
								throw new Exception();
#endif
							this.PassClosingTag(ElementTypes.Null);
						}

						return obj;
					}

				case ElementTypes.Collection:
					return DeserializeCollection(AlreadyInstanciatedCollectionOrDictionary, NumberOfElements.Value, WantedType, e);

				case ElementTypes.Dictionary:
					return DeserializeDictionary(AlreadyInstanciatedCollectionOrDictionary, NumberOfElements.Value, WantedType, e);

				default:
					byte code = (byte)e.ElementType;
					throw new NotImplementedException(code.ToString());
					throw new Exception();
			}
			throw new NotImplementedException();

		}
		internal static readonly int StringTypeCode = (int)Type.GetTypeCode(typeof(string));

		// --------------------------------------------------

		Object DeserializeCollection(
			object collection,
			long NumberOfElements,
			L3TypeManager WantedType,
			Element e
			)
		{
#if DEBUG
			if (collection == null || WantedType == null)
				Debugger.Break();
#endif
			IList list = collection as IList;
			if (list == null)
			{
				Type ItemType;
				list = Tools.GetIListBoxer(collection as IEnumerable, out ItemType);
			}

			Array array = collection as Array;
			bool is1DArray = array != null && array.Rank == 1;
#if DEBUG
			if (array != null && array.Rank != 1)
				throw new Exception("This version of UniversalSerializer can not manage multi-dimentionnal arrays.");
#endif
			L3TypeManager itemsType = WantedType.CollectionItemsTypeManager;// Manager.Value;
#if false
				if (itemsType == null)
					Debugger.Break();
#endif

			for (int i = 0; i < NumberOfElements; i++)
			{
				var item = this.LookForObjectAndManagesChannels(
					itemsType
, null
, null
, false
);

				if (is1DArray)
					list[i] = item;
				else
					list.Add(item);
			}
			if (e.NeedsAnEndElement)
				this.PassClosingTag(ElementTypes.Collection);


			return collection;
		}

		// --------------------------------------------------

		Object DeserializeDictionary(
			object dictionary,
			long NumberOfElements,
			L3TypeManager WantedType,
			Element e
			)
		{
#if DEBUG
			if (dictionary == null || WantedType == null)
				Debugger.Break();
#endif
			IDictionary dict = Tools.GenericIDictionaryBoxer<int, int>.CreateFromGenericIDictionary(dictionary, WantedType);

			L3TypeManager keysType = WantedType.DictionaryKeysTypeManager; // can be null, for not-generic dictionaries.
			L3TypeManager ValuesType = WantedType.DictionaryValuesTypeManager; // can be null, for not-generic dictionaries.

			for (int i = 0; i < NumberOfElements; i++)
			{
				var key = this.LookForObjectAndManagesChannels(keysType, null, null, false);
				var Value = this.LookForObjectAndManagesChannels(ValuesType, null, null, false);

				dict.Add(key, Value);
			}
			if (e.NeedsAnEndElement)
				this.PassClosingTag(ElementTypes.Dictionary);

			return dictionary;
		}

		// --------------------------------------------------

		Object DeserializeSubBranch(
			Element e, L3TypeManager WantedType,
			object AlreadyInstanciatedCollectionOrDictionary, // For inner collection or dictionary.
			bool AddToTheInstances)
		{
			L3TypeManager typeManager =
				e.typeIndexIsKnown && (WantedType == null || (WantedType != null && e.typeIndex != WantedType.TypeIndex)) ?
				this.typeManagerCollection.GetTypeManager(e.typeIndex)
				: WantedType;

			// TODO: try to dynamically build a method that deserializes this particular type by this formatter.

			// Two different ways: with a default constructor or with a parametric constructor.
			// A parametric constructor needs to deserialize fields and properties first.

			Object obj = AlreadyInstanciatedCollectionOrDictionary;
#if DEBUG
			if (AlreadyInstanciatedCollectionOrDictionary != null && AddToTheInstances)
				Debugger.Break();
#endif

			if (obj == null)
				obj = typeManager.l2TypeManager.ConstructNewObjectAndSetMembers(typeManager, this, AddToTheInstances, e.NumberOfElements);

			#region Now deserializes the inner collection
			if (typeManager.l2TypeManager.L1TypeManager.IsAnObjectIEnumerable)
			{
				if (AlreadyInstanciatedCollectionOrDictionary == null)
				{
#if DEBUG
					if (e.NumberOfElements == null)
						Debugger.Break();
#endif

#if DEBUG
					IEnumerable collection = (IEnumerable)
#endif
this.LookForObjectAndManagesChannels(
							typeManager
, obj
, e.NumberOfElements
, false
);
				}

			}
			#endregion Now deserializes the inner collection


			#region Now deserializes the inner dictionary
			if (typeManager.l2TypeManager.L1TypeManager.IsADictionary)
				if (AlreadyInstanciatedCollectionOrDictionary == null)
				{
#if DEBUG
					if (e.NumberOfElements == null)
						Debugger.Break();
#endif

#if DEBUG
					IEnumerable dictionary = (IEnumerable)
#endif
this.LookForObjectAndManagesChannels(
							typeManager
, obj
, e.NumberOfElements
, false
);
				}
			#endregion Now deserializes the inner dictionary

			this.PassClosingTag(ElementTypes.SubBranch);
			return obj;
		}

		// ---------------------------------

		[System.Runtime.CompilerServices.MethodImpl((System.Runtime.CompilerServices.MethodImplOptions)256)] // AggressiveInlining
		internal object ConstructAndSetMembers<T>(
			L3TypeManager l3TypeManager,
			bool AddToTheInstances, long? NumberOfElements)
		{
			L2GenericTypeManager<T> typeManager = l3TypeManager.l2TypeManager as L2GenericTypeManager<T>;

			T obj;
			var gtd = typeManager.L1TypeManager;

			if (gtd.type.IsArray)
			{
				object array = Array.CreateInstance(typeManager.CollectionItemsTypeManager.L1TypeManager.type,
#if SILVERLIGHT || PORTABLE
                        checked((int)NumberOfElements.Value)
#else
 NumberOfElements.Value
#endif
);
				if (AddToTheInstances)
					this.instancesChannel.Instances.Add(array); // Add the instance before deserializing inner data.
				return array;
			}
			else
				if (typeManager.DefaultConstructor != null)
				{
					#region Build from a default (no-param) constructor

					// creates an instance (or a value if it is a structure):
#if SILVERLIGHT
						if (typeManager.L1TypeManager.type.IsEnum)
							obj = default(T);
						else
#endif
					obj = (T)typeManager.DefaultConstructor();

					if (AddToTheInstances)
						this.instancesChannel.Instances.Add(obj); // Add the instance before deserializing inner data.

					if (typeManager.SelectedFields != null)
						typeManager.SetFieldValues(ref obj,
								GetFieldValues(l3TypeManager));

					if (typeManager.SelectedProperties != null)
						typeManager.SetPropertyValues(ref obj,
							GetPropertyValues(l3TypeManager));

					#endregion Build from a default (no-param) constructor
				}
				else
					if (typeManager.parametricConstructorDescriptor != null)
					#region Build from a parametric constructor
					{

						int instanceIndex = 0;
						if (AddToTheInstances)
						{
							instanceIndex = this.instancesChannel.Instances.Count;
							this.instancesChannel.Instances.Add(null); // reserves site.
						}


						object[] fieldValues =
						 (typeManager.SelectedFields != null) ?
								GetFieldValues(l3TypeManager)
								: new object[0];

						object[] propValues =
						 (typeManager.SelectedProperties != null) ?
								GetPropertyValues(l3TypeManager)
								: new object[0];

#if false // Future use, when the parameter indexes will be on both fields and properties.
							object[] fieldsAndProps = new object[fieldValues.Length + propValues.Length];
							fieldValues.CopyTo(fieldsAndProps, 0);
							propValues.CopyTo(fieldsAndProps, fieldValues.Length);
#endif
						// TODO: check if the deserialized ParametricConstructorDescriptor is the same as the TypeManager's one.

						int nbPars = typeManager.parametricConstructorDescriptor.ParameterFields.Length;
						object[] parameters = new object[nbPars];
						for (int ipars = 0; ipars < nbPars; ipars++)
							parameters[ipars] = fieldValues[typeManager.ParametricConstructorFieldParameterIndexes[ipars]];

						// I do not use a Linq Expression compiled constructor because it takes a lot of time to be produced.
						// A reflection constructor call is slower but it has no creation time.
						obj = (T)typeManager.parametricConstructorDescriptor.constructorInfo.Invoke(parameters);

						if (AddToTheInstances)
							this.instancesChannel.Instances[instanceIndex] = obj;

						if (fieldValues.Length > 0)
							typeManager.SetFieldValues(ref obj, fieldValues);

						if (propValues.Length > 0)
							typeManager.SetPropertyValues(ref obj, propValues);
					}
					#endregion Build from a parametric constructor
					else
					{
						string msg = string.Format("No exploitable constructor for type {0}", gtd.type.FullName);
						Log.WriteLine(msg);
						throw new Exception(msg); // No valid constructor.
					}

			return obj;
		}

		// --------------------------------------------------

		object[] GetFieldValues(L3TypeManager typeManager)
		{
			if (typeManager.SelectedFieldTypeManagers == null)
				return new object[0];

			int l = typeManager.SelectedFieldTypeManagers.Length;

			object[] fieldValues = new object[l];

			for (int ifi = 0; ifi < l; ifi++)
			{
				var _tm = typeManager.SelectedFieldTypeManagers[ifi];
				if (_tm == null)
				{
					_tm =
						typeManager.SelectedFieldTypeManagers[ifi] =
							this.typeManagerCollection.GetTypeManager(
							_tm.l2TypeManager.L1TypeManager.type,
							null, true, true);
					typeManager.l2TypeManager.SelectedFieldTypeManagers[ifi] =
						_tm.l2TypeManager;
				}
				var fieldValue = this.LookForObjectAndManagesChannels(
					_tm
, null
, null
, false
);
				fieldValues[ifi] = fieldValue;
			}
			return fieldValues;
		}

		// --------------------------------------------------

		// --------------------------------------------------

		object[] GetPropertyValues(L3TypeManager typeManager)
		{
			if (typeManager.SelectedPropertyTypeManagers == null)
				return new object[0];

			int l = typeManager.SelectedPropertyTypeManagers.Length;
			object[] propertyValues = new object[l];

			for (int ipi = 0; ipi < l; ipi++)
			{
				var _tm = typeManager.SelectedPropertyTypeManagers[ipi];
				if (_tm == null)
				{
					_tm =
						typeManager.SelectedPropertyTypeManagers[ipi] =
							this.typeManagerCollection.GetTypeManager(
							typeManager.l2TypeManager.L1TypeManager.type,
							null, true, true);
					typeManager.l2TypeManager.SelectedPropertyTypeManagers[ipi] = _tm.l2TypeManager;
				}
				var propValue = this.LookForObjectAndManagesChannels(_tm, null, null, false);

				propertyValues[ipi] = propValue;
			}
			return propertyValues;
		}

		// --------------------------------------------------
		// --------------------------------------------------
		// --------------------------------------------------
		// --------------------------------------------------


		// ######################################################################

		#endregion Sub-Deserializer

		// ######################################################################
		// ######################################################################

		internal class InstancesChannel
		{
			internal readonly List<object> Instances = new List<object>();

			internal InstancesChannel()
			{
			}
		}
	}


	// ######################################################################
	// ######################################################################


}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under Microsoft Reciprocal License


Written By
Software Developer (Senior) independent
France France
Hi !

I made my first program on a Sinclair ZX-81.
Since that day, I have the virus of computers. Smile | :)

Here is my website:
https://chrisbertrand.net

And my blog:
https://chrisbertrandprogramer.wordpress.com/

Comments and Discussions