Click here to Skip to main content
15,884,986 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 257.7K   4K   299  
An universal and easy serialization library for .NET and .NET Core.


// Copyright Christophe Bertrand.

#if !JSON_DISABLED // Temporarily disabled

using System;
using System.Collections.Generic;
#if DEBUG
using System.Diagnostics;
#endif
using System.Xml;
using System.Runtime.Serialization.Json;
using System.IO;
using System.Text;
using UniversalSerializerLib2.TypeManagement;

namespace UniversalSerializerLib2
{

	// Why the hell C# can not use strings in enums ????
	/// <summary>
	/// Follows the order of ElementTypes.
	/// </summary>
	struct JSONElementName
	{
		internal const string TypesChannelSection = "s0"; // When we insert an object at the root of this branch.
		internal const string InstancesChannelSection = "s1"; // When we insert an object at the root of this branch.
		internal const string PrimitiveValue = "p";
		internal const string Reference = "r";
		internal const string Null = "z";
		internal const string DefaultValue = "f";
		internal const string SubBranch = "b";
		internal const string Collection = "c";
		internal const string Dictionary = "d";
	};

	/// <summary>
	/// Follows the order of AttributeTypes.
	/// In JSON, it is essential that attributes names are different from any element name, as in JSON attributes will be serialized as elements.
	/// </summary>
	struct JSONAttributeName
	{
		internal const string InstanceIndex = "i";
		internal const string TypeNumber = "t";
		internal const string NumberOfElements = "l"; // for collections and dictionaries.
#if DEBUG
		internal const string Name = "n";
		internal const string TypeName = "typeName"; // Only for debugging.
#endif
	}

	internal class JSONSerializationFormatter : SerializationFormatter
	{
		internal const string DataEndMarkTag = "end";

		internal const string DocumentPreamble = "{\n\"version\": \"2.0\",";

		internal override bool IsStringFormatter { get { return true; } } // As JSON or JSON. Otherwize, it is a binary formatter.

		/// <summary>
		/// Follows the order of ElementTypes and JSONElementName.
		/// </summary>
		internal static readonly string[] JSONElementNames = new string[]
		{
			JSONElementName.TypesChannelSection,//  "s0", // When we insert an object at the root of this branch.
			JSONElementName.InstancesChannelSection, //  "s1", // When we insert an object at the root of this branch.
			JSONElementName.PrimitiveValue , // "e",
			JSONElementName.Reference, // "r",
			JSONElementName.Null, // "z",
			JSONElementName.DefaultValue, // "f",
			JSONElementName.SubBranch, // "b",
			JSONElementName.Collection, // "c",
			JSONElementName.Dictionary // "d",
		};

		/// <summary>
		/// Follows the order of JSONAttributeName and of AttributeTypes.
		/// </summary>
		internal static readonly string[] JSONAttributeNames = new string[]
		{
			JSONAttributeName.InstanceIndex,// "i",
			JSONAttributeName.TypeNumber, // "t",
			JSONAttributeName.NumberOfElements, // "l", // for collections and dictionaries.
#if DEBUG
			JSONAttributeName.Name, // "n",
			JSONAttributeName.TypeName // "typeName" // Only for debugging.
#endif
		};

		internal override bool CanManageMultiplexStreams
		{
			get
			{
				return true;
			}
		}

		/// <summary>
		/// Compulasary to place comas at the right places (specific to JSON).
		/// That works because this serializer is a total serial process. That would not work with a threaded serializer. It is not a hack, it is an optimisation.
		/// </summary>
		internal bool CurrentSubBranchIsEmptyYet = true;

		internal JSONSerializationFormatter(Parameters parameters)
			: base(parameters)
		{
		}

		internal override void StartTree()
		{
			this.WriteStringToAssembledOrMultiplexStream(DocumentPreamble);
		}

		protected override void EndTree()
		{
			this.WriteStringToAssembledOrMultiplexStream("\n}");
		}

		internal override void InsertDataEndMark()
		{
			this.WriteStringToAssembledOrMultiplexStream(
				",\n\""+ JSONSerializationFormatter.DataEndMarkTag+ "\":\"\"");
		}

		void WriteStringToAssembledOrMultiplexStream(string s)
		{
			this.streamWriter.Write(s);
		}

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

			/// <summary>
			/// Returns true if the element has already been closed.
			/// </summary>
			/// <param name="element"></param>
			/// <param name="Value"></param>
			/// <param name="channelInfos"></param>
			/// <param name="IsStructure"></param>
			/// <param name="TheElementHaveAttributes"></param>
			/// <param name="TypeIndex"></param>
			/// <returns></returns>
			internal override bool ChannelEnterElementAndWriteValue(
				ref ChannelInfos channelInfos, 
				Element element
, out bool TheElementHaveAttributes
				, TypeCode TypeIndex
				, object Value,
				bool IsStructure)
			{
				StringBuilder s = new StringBuilder();
				if (!this.CurrentSubBranchIsEmptyYet)
					s.Append(',');

				this.CurrentSubBranchIsEmptyYet = !element.IsAClosingTag;

				s.Append("\n\"");
				s.Append(JSONSerializationFormatter.JSONElementNames[(int)element.ElementType]);
				s.Append("\": {");

				bool IsThereAnAttribute = false;

				IsThereAnAttribute |= _enterElementAttribute(s, AttributeTypes.InstanceIndex, element.InstanceIndex, !IsThereAnAttribute);
				if (element.typeIndexIsKnown)
					IsThereAnAttribute |= _enterElementAttribute(s, AttributeTypes.TypeNumber, element.typeIndex, !IsThereAnAttribute);
				{
					// special in JSON: we do not write the string length except if it 0 (to differenciate String.Empty and null at least).
					// The reason is JSON can determine string length by itself (contrary to binary formatters).
					long? noe =
						element.NumberOfElements != null ?
						(element.ElementType == ElementTypes.PrimitiveValue ? (element.NumberOfElements.Value == 0 ? element.NumberOfElements : null)
						: element.NumberOfElements)
						: null;
					IsThereAnAttribute |= _enterElementAttribute(s, AttributeTypes.NumberOfElements, noe, !IsThereAnAttribute);
				}

				this.CurrentSubBranchIsEmptyYet &= !IsThereAnAttribute;

				bool closeElement = element.IsAClosingTag;
				if (closeElement)
					s.Append("}");

				this.ChannelWriteStringToStream(ref channelInfos, s.ToString());

					if (Value != null)
						this.ChannelWriteValueAsString(ref channelInfos, (string)Value, IsThereAnAttribute);


				TheElementHaveAttributes = IsThereAnAttribute;
				return closeElement;
			}
#if DEBUG_WriteCounters
			int typeCounter = SerializationFormatter.TypeIndexationStart;
			int instanceCounter;
#endif

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

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

			static bool _enterElementAttribute(StringBuilder s, AttributeTypes AttributeType, object value, bool firstAttributeOrSubBranch)
			{
				if (value == null)
					return false;

				if (!firstAttributeOrSubBranch)
					s.Append(",");
				s.Append("\n\"");
				s.Append(JSONSerializationFormatter.JSONAttributeNames[(int)AttributeType]);
				s.Append("\": \"");
				s.Append(Tools.TranscodeToJSONCompatibleString(value.ToString()));
				s.Append("\"");
				return true;
			}

			static bool _enterElementAttribute(StringBuilder s, string AttributeName, string value, bool firstAttributeOrSubBranch)
			{
				if (value == null)
					return false;

				if (!firstAttributeOrSubBranch)
					s.Append(",");
				s.Append("\n\"");
				s.Append(AttributeName);
				s.Append("\": \"");
				s.Append(value);
				s.Append("\"");
				return true;
			}

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

			internal override void ChannelExitElement(ref ChannelInfos channelInfos, ElementTypes elementType)
			{
				this.ChannelWriteStringToStream(ref channelInfos, "\n}");
			}

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

			internal override string ChannelPrepareStringFromSimpleValue(ref ChannelInfos channelInfos, object value)
			{
				string StringValue = value as string;
				if (StringValue != null)
					return Tools.TranscodeToJSONCompatibleString(StringValue); // transcodes JSON syntax characters.

				return this.TranscodeSimpleValueToString(value);
			}

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

			internal override void ChannelWriteValueAsString(ref ChannelInfos channelInfos, string value, bool TheElementHaveAttributes)
			{
				StringBuilder sb = new StringBuilder();
				_enterElementAttribute(sb,
					string.Empty, // The Value attribute has no name.
					value, !TheElementHaveAttributes);
				this.ChannelWriteStringToStream(ref channelInfos, sb.ToString());

				this.CurrentSubBranchIsEmptyYet = false;
			}

		//}

		// ######################################################################
	}

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

	internal class JSONDeserializationFormatter : DeserializationFormatter
	{
		XmlReader _JSONReader;

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

		internal JSONDeserializationFormatter(Parameters parameters)
			: base(parameters)
		{
		}

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

		internal override void PassPreamble()
		{
			// Passes the UTF8 prefix "":
			if (stream.Position == 0)
			{
				byte[] prefix = new byte[3];
				int n = this.stream.Read(prefix, 0, 3);
				if (n != 3 || prefix[0] != 'ï' || prefix[1] != '»' || prefix[2] != '¿')
					throw new FormatException("stream");
			}

			// test:
			{
				// We have to build a new JSONReader on each deserialization because it just do not manage multiple json data in one stream correctly.

				XmlDictionaryReaderQuotas quotas = XmlDictionaryReaderQuotas.Max;
				this._JSONReader = JsonReaderWriterFactory.CreateJsonReader(stream,
#if !SILVERLIGHT
 Encoding.UTF8,
#endif
 quotas
#if !SILVERLIGHT
, null
#endif
);
			}

			this._JSONReader.Read();
			this._JSONReader.Read();
			this._JSONReader.Read();
			if (this._JSONReader.Value != "2.0")
				throw new Exception("Version 2.0 expected");
			this._JSONReader.Read();
		}

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

		bool GetNextJSONPart()
		{
			if (WeAlreadyObtainedJSONPart)
			{
				WeAlreadyObtainedJSONPart = false;
				return true;
			}
			try
			{
				bool ok = this._JSONReader.Read();
				return ok;
			}
			catch (Exception ex)
			{
				if (ex is XmlException && this._JSONReader.NodeType == XmlNodeType.EndElement && this._JSONReader.Name == "root")
				{
					// JSONReader complains about multiple docs in the same stream, but it is not a real problem, we only ignore this exception.
					return true;
				}
				throw;
			}
		}
		bool WeAlreadyObtainedJSONPart = false;

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

		/// <summary>
		/// If the right element is not found, an exception occures.
		/// We can propose one or two element types.
		/// </summary>
		internal override bool GetNextElementAs(
			ref Element e,
			bool AcceptChannelSection, string SupposedName, L3TypeManager SupposedType, bool NeedsThisElement, bool WantClosingElement
)
		{
			int? SupposedTypeNumber = SupposedType != null ? SupposedType.TypeIndex : (int?)null;

			if (!this.GetNextJSONPart())
				if (NeedsThisElement)
					throw new Exception();
				else
					return false;

			if (this._JSONReader.Name == JSONSerializationFormatter.DataEndMarkTag)
			{
				WeAlreadyObtainedJSONPart = true;
				this.PassDataEndMark();
				return false;
			}

			ElementTypes? et2 = this.GetElementType();
			if (et2 == null)
			{
#if DEBUG
				if (this._JSONReader.NodeType != XmlNodeType.EndElement && this._JSONReader.Name != "data")
					Debugger.Break();
#endif
				return false;
			}
			ElementTypes et = et2.Value;

			if (WantClosingElement && this._JSONReader.NodeType == XmlNodeType.Element)
				if (NeedsThisElement)
					throw new Exception();
				else
					return false;

#if DEBUG
			if (!AcceptChannelSection
						&& !(et == ElementTypes.InstancesChannelSection || et == ElementTypes.TypesChannelSection))
				throw new Exception();
#endif



#region Analyse attributes
			// Contrary to xml, json does not have attributes. We have to take the right elements as attribute replacement, analysing its name.

			int? tn = null;
			int? InstanceNumber = null;
			long? numberOfElements = null;
#if DEBUG
			string name = null;
#endif

			while (this.GetNextJSONPart())
			{
				int iattribute = ((IList<string>)JSONSerializationFormatter.JSONAttributeNames).IndexOf(this._JSONReader.Name);
				if (iattribute < 0)
					break;

				{
					AttributeTypes at = (AttributeTypes)iattribute; // more clear.

					switch (at)
					{

						case AttributeTypes.InstanceIndex:
							{
								string inum = this.JSONGetValue();
								InstanceNumber =
									inum != null ?
									int.Parse(inum)
									: (int?)null;
							}
							break;


						case AttributeTypes.TypeNumber:
							{
								string typeNumber = this.JSONGetValue();
#if DEBUG
								// Checks type correspondance:
								if (typeNumber != null && typeNumber != string.Empty && SupposedTypeNumber != null)
								{
									int foundTypeNumber = int.Parse(typeNumber);
									if (foundTypeNumber != SupposedTypeNumber.Value)
									{
										var found = base.typeManagerCollection.GetTypeManager(foundTypeNumber);
										var foundGtd = found.l2TypeManager.L1TypeManager;
										if (!foundGtd.type.Is(typeof(ITypeContainer)))
										{
											var supposed = base.typeManagerCollection.GetTypeManager(SupposedTypeNumber.Value);
											var supposedGtd = supposed.l2TypeManager.L1TypeManager;
											if (!foundGtd.type.Is(supposedGtd.type))
												if (!supposedGtd.IsNullable || supposedGtd.type.GetGenericArguments().Length == 0 || !foundGtd.type.Is(supposedGtd.type.GetGenericArguments()[0]))
													throw new Exception();
										}
									}
								}
#endif
								tn =
									typeNumber != null ? (
									int.Parse(typeNumber))
									: SupposedTypeNumber;
							}
							break;

						case AttributeTypes.NumberOfElements:
							{
								string noe = this.JSONGetValue();
								numberOfElements =
									noe != null ?
									long.Parse(noe)
									: (long?)null;
							}
							break;

#if DEBUG
						case AttributeTypes.Name:
							{
								name = this.JSONGetValue();

								// Checks name correspondance:
								if (SupposedName != null && name != null && name != SupposedName)
									throw new Exception();
							}
							break;

						case AttributeTypes.TypeName:
							{
								string typeName = this.JSONGetValue();
							}
							break;
						default:
							throw new Exception();
#endif
					}
				}
			}
			WeAlreadyObtainedJSONPart = true; // the previous read element is kept for following process.
#endregion Analyse attributes

			this.GetNextJSONPart();
			this.WeAlreadyObtainedJSONPart=true;
			bool ContainsAValue = this._JSONReader.NodeType != XmlNodeType.EndElement;

			e.ContainsAValue = ContainsAValue;		
			e.ElementType = et;
			e.InstanceIndex = InstanceNumber;
			e.IsAClosingTag = this._JSONReader.NodeType == XmlNodeType.EndElement;			
#if DEBUG
			e.Name= name != null ? name : SupposedName;
#endif
			e.NeedsAnEndElement = !this._JSONReader.IsEmptyElement;
			e.NumberOfElements = numberOfElements;
			if (tn != null)
			{
				e.typeIndex = tn.Value;
				e.typeIndexIsKnown = true;
			}
			return true;
		}

		string JSONGetValue()
		{
			this.GetNextJSONPart();
#if DEBUG
			if (this._JSONReader.NodeType != XmlNodeType.Text)
				throw new Exception();
#endif
			string ret = this._JSONReader.Value;
			this.GetNextJSONPart(); // Passes EndElement.
			return ret;
		}

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

		/// <summary>
		/// Current this.JSONReader to ElementTypes?.
		/// </summary>
		ElementTypes? GetElementType()
		{
			if (this._JSONReader.NodeType != XmlNodeType.Element)
				return null;
			string en = this._JSONReader.Name;
			int i = (JSONSerializationFormatter.JSONElementNames as IList<string>).IndexOf(this._JSONReader.Name);
			if (i < 0)
				return null;
			ElementTypes et = (ElementTypes)i;
			return et;
		}

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

		internal override void PassClosingTag(ElementTypes ElementType)
		{
			this.GetNextJSONPart();
			if (this._JSONReader.Name == JSONSerializationFormatter.DataEndMarkTag)
			{
				this.WeAlreadyObtainedJSONPart = true;
				return;
			}
#if DEBUG
			if (this._JSONReader.NodeType != XmlNodeType.EndElement
					|| this._JSONReader.Name != JSONSerializationFormatter.JSONElementNames[(int)ElementType])
				throw new Exception(
					string.Format("Unattended JSON element {0} while waiting a closing tag {1}", _JSONReader.Name, ElementType.ToString()));
#endif
		}

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

		internal override Object GetSimpleValueInElement(L3TypeManager typeManager)
		{
			if (!GetNextJSONPart())
				throw new Exception();
			if (this._JSONReader.NodeType == XmlNodeType.EndElement)
			{ // empty 'e' element.
				this.WeAlreadyObtainedJSONPart = true;
				return null;
			}
#if DEBUG
			if (this._JSONReader.NodeType != XmlNodeType.Element || this._JSONReader.Name != "a:item")
				throw new Exception();
#endif

			{
				if (!GetNextJSONPart())
					throw new Exception();
#if DEBUG
				if (this._JSONReader.NodeType != XmlNodeType.Text || this._JSONReader.Name != string.Empty)
					throw new Exception();
#endif
			}

			string rawValue;
			{
				// JsonReaderWriterFactory is very special: it splits string when it encounters a special character.
				// Therfore, we need to concatenate this parts.

				var Texts = new StringBuilder(this._JSONReader.Value);
				while (GetNextJSONPart())
				{
					if (this._JSONReader.NodeType != XmlNodeType.Text)
					{
						this.WeAlreadyObtainedJSONPart = true;
						break;
					}
					Texts.Append(this._JSONReader.Value);
				}
				rawValue = Texts.ToString();
			}

			{ // passes EndElement:
				if (!GetNextJSONPart())
					throw new Exception();
#if DEBUG
				if (this._JSONReader.NodeType != XmlNodeType.EndElement || this._JSONReader.Name != "a:item")
					throw new Exception();
#endif
			}

			return DeserializationFormatter.TranscodeStringToPrimitiveObject(rawValue, typeManager);
		}

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

		internal override void PassTreeEndTag()
		{
			this.GetNextJSONPart();
#if DEBUG
			if (this._JSONReader.NodeType != XmlNodeType.EndElement
					|| this._JSONReader.Name != "root"
				|| this._JSONReader.Value != string.Empty)
				throw new Exception(
					string.Format("Unattended JSON element {0} while waiting a Tree End Tag", _JSONReader.Name));
#endif
		}

		internal override void PassDataEndMark()
		{
			this.GetNextJSONPart();
#if DEBUG
			if (this._JSONReader.NodeType != XmlNodeType.Element
					|| this._JSONReader.Name != JSONSerializationFormatter.DataEndMarkTag
				|| this._JSONReader.Value!=string.Empty)
				throw new Exception(
					string.Format("Unattended JSON element {0} while waiting a Data End Mark", _JSONReader.Name));
#endif	
			this.GetNextJSONPart();
#if DEBUG
			if (this._JSONReader.NodeType != XmlNodeType.Text
					|| this._JSONReader.Name != string.Empty
				|| this._JSONReader.Value != string.Empty)
				throw new Exception(
					string.Format("Unattended JSON element {0} while waiting a Data End Mark", _JSONReader.Name));
#endif
			this.GetNextJSONPart();
#if DEBUG
			if (this._JSONReader.NodeType != XmlNodeType.EndElement
					|| this._JSONReader.Name != JSONSerializationFormatter.DataEndMarkTag
				|| this._JSONReader.Value != string.Empty)
				throw new Exception(
					string.Format("Unattended JSON element {0} while waiting a Data End Mark", _JSONReader.Name));
#endif
		}
	}

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

}

#endif

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