diff HowTo/Mapping/MapToJson.cs @ 0:f990fcb411a9

Копия текущей версии из github
author cin
date Thu, 27 Mar 2014 21:46:09 +0400
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/HowTo/Mapping/MapToJson.cs	Thu Mar 27 21:46:09 2014 +0400
@@ -0,0 +1,435 @@
+using System;
+using System.Globalization;
+using System.Text;
+using System.Xml;
+
+using NUnit.Framework;
+
+using BLToolkit.Mapping;
+using BLToolkit.Reflection;
+
+namespace HowTo.Mapping
+{
+	public class JsonMapper : MapDataDestinationBase, IMapDataDestinationList, ISupportMapping
+	{
+		private static readonly long   InitialJavaScriptDateTicks = new DateTime(1970, 1, 1).Ticks;
+
+		private string[]               _fieldNames;
+		private readonly StringBuilder _sb;
+		private MappingSchema          _mappingSchema;
+		private bool                   _scalar;
+		private bool                   _first;
+		private bool                   _firstElement;
+		private int                    _indent;
+
+		public JsonMapper() : this(new StringBuilder(), 0)
+		{
+		}
+
+		public JsonMapper(StringBuilder sb) : this(sb, 0)
+		{
+		}
+
+		public JsonMapper(StringBuilder sb, int indent)
+		{
+			_sb     = sb;
+			_indent = indent;
+		}
+
+		public override Type GetFieldType(int index)
+		{
+			// Same as typeof(object)
+			//
+			return null;
+		}
+
+		public override int GetOrdinal(string name)
+		{
+			return Array.IndexOf(_fieldNames, name);
+		}
+
+		public override void SetValue(object o, int index, object value)
+		{
+			SetValue(o, _fieldNames[index], value);
+		}
+
+		public override void SetValue(object o, string name, object value)
+		{
+			if (!_scalar)
+			{
+				// Do not Json null values until it's an array
+				//
+				if (value == null || (value is XmlNode && IsEmptyNode((XmlNode)value)))
+					return;
+
+				if (_first)
+					_first = false;
+				else
+					_sb
+						.Append(',')
+						.AppendLine()
+						;
+
+				for (int i = 0; i < _indent; ++i)
+					_sb.Append(' ');
+
+				_sb
+					.Append('"')
+					.Append(name)
+					.Append("\":")
+					;
+			}
+
+			if (value == null)
+				_sb.Append("null");
+			else
+			{
+				switch (Type.GetTypeCode(value.GetType()))
+				{
+					case TypeCode.Empty:
+					case TypeCode.DBNull:
+						_sb.Append("null");
+						break;
+					case TypeCode.Boolean:
+						_sb.Append((bool)value? "true": "false");
+						break;
+					case TypeCode.Char:
+						_sb
+							.Append('\'')
+							.Append((char)value)
+							.Append('\'')
+							;
+						break;
+					case TypeCode.SByte:
+					case TypeCode.Int16:
+					case TypeCode.Int32:
+					case TypeCode.Int64:
+					case TypeCode.Byte:
+					case TypeCode.UInt16:
+					case TypeCode.UInt32:
+					case TypeCode.UInt64:
+					case TypeCode.Single:
+					case TypeCode.Double:
+					case TypeCode.Decimal:
+						_sb.Append(((IFormattable)value).ToString(null, CultureInfo.InvariantCulture));
+						break;
+					case TypeCode.DateTime:
+						_sb
+							.Append("new Date(")
+							.Append((((DateTime)value).Ticks - InitialJavaScriptDateTicks)/10000)
+							.Append(")");
+						break;
+					case TypeCode.String:
+						_sb
+							.Append('"')
+							.Append(encode((string)value))
+							.Append('"')
+							;
+						break;
+					default:
+						if (value is XmlNode)
+						{
+							if (IsEmptyNode((XmlNode) value))
+								_sb.Append("null");
+							else
+								WriteXmlJson((XmlNode)value);
+						}
+						else
+						{
+							JsonMapper inner = new JsonMapper(_sb, _indent + 1);
+
+							if (value.GetType().IsArray)
+								_mappingSchema.MapSourceListToDestinationList(
+									_mappingSchema.GetDataSourceList(value), inner);
+							else
+								_mappingSchema.MapSourceToDestination(
+									_mappingSchema.GetDataSource(value), value, inner, inner);
+						}
+						break;
+				}
+			}
+		}
+
+		private static string encode(string value)
+		{
+			return value.Replace("\r\n", "\\r")
+				.Replace("\n\r", "\\r")
+				.Replace("\n", "\\r")
+				.Replace("\r", "\\r")
+				.Replace("\"","\\\"");
+		}
+
+		private void WriteXmlJson(XmlNode node)
+		{
+			XmlNode textNode = GetTextNode(node);
+			if (textNode != null)
+			{
+				_sb
+					.Append("\"")
+					.Append(encode(textNode.Value))
+					.Append('\"')
+					;
+			}
+			else
+			{
+
+				bool first = true;
+
+				_sb.Append('{');
+
+				if (node.Attributes != null)
+				{
+					foreach (XmlAttribute attr in node.Attributes)
+					{
+						if (first)
+							first = false;
+						else
+							_sb.Append(',');
+
+						_sb
+							.Append("\"@")
+							.Append(attr.Name)
+							.Append("\":\"")
+							.Append(encode(attr.Value))
+							.Append('\"')
+							;
+					}
+				}
+
+				foreach (XmlNode child in node.ChildNodes)
+				{
+					if (IsWhitespace(child) || IsEmptyNode(child))
+						continue;
+
+					if (first)
+						first = false;
+					else
+						_sb.Append(',');
+
+					if (child is XmlText)
+						_sb
+							.Append("\"#text\":\"")
+							.Append(encode(child.Value))
+							.Append('\"')
+							;
+					else if (child is XmlElement)
+					{
+						_sb
+							.Append('"')
+							.Append(child.Name)
+							.Append("\":")
+							;
+						WriteXmlJson(child);
+					}
+					else
+						System.Diagnostics.Debug.Fail("Unexpected node type " + child.GetType().FullName);
+				}
+				_sb.Append('}');
+			}
+		}
+
+		private static bool IsWhitespace(XmlNode node)
+		{
+			switch (node.NodeType)
+			{
+				case XmlNodeType.Comment:
+				case XmlNodeType.Whitespace:
+				case XmlNodeType.SignificantWhitespace:
+					return true;
+			}
+			return false;
+		}
+
+		private static bool IsEmptyNode(XmlNode node)
+		{
+			if (node.Attributes != null && node.Attributes.Count > 0)
+				return false;
+
+			if (node.HasChildNodes)
+				foreach (XmlNode childNode in node.ChildNodes)
+				{
+					if (IsWhitespace(childNode) || IsEmptyNode(childNode))
+						continue;
+
+					// Not a whitespace, nor inner empty node.
+					//
+					return false;
+				}
+
+			return node.Value == null;
+		}
+
+		private static XmlNode GetTextNode(XmlNode node)
+		{
+			if (node.Attributes != null && node.Attributes.Count > 0)
+				return null;
+
+			XmlNode textNode = null;
+
+			foreach (XmlNode childNode in node.ChildNodes)
+			{
+				// Ignore all whitespace.
+				//
+				if (IsWhitespace(childNode))
+					continue;
+
+				if (childNode is XmlText)
+				{
+					// More then one text node.
+					//
+					if (textNode != null)
+						return null;
+
+					// First text node.
+					//
+					textNode = childNode;
+				}
+				else
+					// Not a text node - break;
+					//
+					return null;
+			}
+
+			return textNode;
+		}
+
+		#region ISupportMapping Members
+
+		void ISupportMapping.BeginMapping(InitContext initContext)
+		{
+			_first         = true;
+			_mappingSchema = initContext.MappingSchema;
+			_fieldNames    = new string[initContext.DataSource.Count];
+
+			for (int i = 0; i < _fieldNames.Length; ++i)
+				_fieldNames[i] = initContext.DataSource.GetName(i);
+
+			_scalar = _fieldNames.Length == 1 && string.IsNullOrEmpty(_fieldNames[0]);
+
+			if (_scalar)
+				return;
+
+			if (_fieldNames.Length <= 1)
+			{
+				// Reset the indent since output is a single line.
+				//
+				_indent = 0;
+				_sb.Append('{');
+			}
+			else
+			{
+				if (_indent > 0)
+					_sb.AppendLine();
+
+				for (int i = 0; i < _indent; ++i)
+					_sb.Append(' ');
+
+				_sb
+					.Append('{')
+					.AppendLine()
+					;
+			}
+		}
+
+		void ISupportMapping.EndMapping(InitContext initContext)
+		{
+			if (_scalar)
+				return;
+
+			if (_fieldNames.Length > 1)
+				_sb.AppendLine();
+
+			for (int i = 0; i < _indent; ++i)
+				_sb.Append(' ');
+			_sb.Append('}');
+		}
+
+		#endregion
+
+		#region IMapDataDestinationList Members
+
+		void IMapDataDestinationList.InitMapping(InitContext initContext)
+		{
+			_firstElement = true;
+			_sb.Append('[');
+		}
+
+		IMapDataDestination IMapDataDestinationList.GetDataDestination(InitContext initContext)
+		{
+			return this;
+		}
+
+		object IMapDataDestinationList.GetNextObject(InitContext initContext)
+		{
+			if (_firstElement)
+				_firstElement = false;
+			else
+				_sb.Append(',');
+
+			return this;
+		}
+
+		void IMapDataDestinationList.EndMapping(InitContext initContext)
+		{
+			_sb.Append(']');
+		}
+
+		#endregion
+
+		public override string ToString()
+		{
+			return _sb.ToString();
+		}
+	}
+
+	[TestFixture]
+	public class MapToJson
+	{
+		public class Inner
+		{
+			public string Name = "inner \"object \n name";
+		}
+
+		public class Inner2
+		{
+			public string Name;
+			public int    Value;
+		}
+
+		public class SourceObject
+		{
+			public string   Foo = "Foo";
+			public double   Bar  = 1.23;
+			public DateTime Baz  = DateTime.Today;
+			[MapIgnore(false)]
+			public Inner    Inner = new Inner();
+			[MapIgnore(false)]
+			public Inner2   Inner2 = new Inner2();
+			public string[] StrArray = {"One", "Two", "Three"};
+		}
+
+		[Test]
+		public void Test()
+		{
+			JsonMapper jm = new JsonMapper(new StringBuilder(256));
+
+			Map./*[a]*/MapSourceToDestination/*[/a]*/(Map.GetObjectMapper(typeof(SourceObject)), new SourceObject(), jm, jm);
+			Console.Write(jm.ToString());
+
+			// Expected output:
+			//
+			// {
+			// "Foo":"Foo",
+			// "Bar":1.23,
+			// "Baz":new Date(11823840000000000),
+			// "Inner":{ "Name":"inner \"object \r name"},
+			// "Inner2":
+			//  {
+			//  "Name":null,
+			//  "Value":0
+			//  },
+			//  "StrArray":["One","Two","Three"]
+			// }
+		}
+	}
+}