Mercurial > pub > bltoolkit
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"] + // } + } + } +}