Mercurial > pub > bltoolkit
comparison HowTo/Mapping/MapToJson.cs @ 0:f990fcb411a9
Копия текущей версии из github
| author | cin |
|---|---|
| date | Thu, 27 Mar 2014 21:46:09 +0400 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:f990fcb411a9 |
|---|---|
| 1 using System; | |
| 2 using System.Globalization; | |
| 3 using System.Text; | |
| 4 using System.Xml; | |
| 5 | |
| 6 using NUnit.Framework; | |
| 7 | |
| 8 using BLToolkit.Mapping; | |
| 9 using BLToolkit.Reflection; | |
| 10 | |
| 11 namespace HowTo.Mapping | |
| 12 { | |
| 13 public class JsonMapper : MapDataDestinationBase, IMapDataDestinationList, ISupportMapping | |
| 14 { | |
| 15 private static readonly long InitialJavaScriptDateTicks = new DateTime(1970, 1, 1).Ticks; | |
| 16 | |
| 17 private string[] _fieldNames; | |
| 18 private readonly StringBuilder _sb; | |
| 19 private MappingSchema _mappingSchema; | |
| 20 private bool _scalar; | |
| 21 private bool _first; | |
| 22 private bool _firstElement; | |
| 23 private int _indent; | |
| 24 | |
| 25 public JsonMapper() : this(new StringBuilder(), 0) | |
| 26 { | |
| 27 } | |
| 28 | |
| 29 public JsonMapper(StringBuilder sb) : this(sb, 0) | |
| 30 { | |
| 31 } | |
| 32 | |
| 33 public JsonMapper(StringBuilder sb, int indent) | |
| 34 { | |
| 35 _sb = sb; | |
| 36 _indent = indent; | |
| 37 } | |
| 38 | |
| 39 public override Type GetFieldType(int index) | |
| 40 { | |
| 41 // Same as typeof(object) | |
| 42 // | |
| 43 return null; | |
| 44 } | |
| 45 | |
| 46 public override int GetOrdinal(string name) | |
| 47 { | |
| 48 return Array.IndexOf(_fieldNames, name); | |
| 49 } | |
| 50 | |
| 51 public override void SetValue(object o, int index, object value) | |
| 52 { | |
| 53 SetValue(o, _fieldNames[index], value); | |
| 54 } | |
| 55 | |
| 56 public override void SetValue(object o, string name, object value) | |
| 57 { | |
| 58 if (!_scalar) | |
| 59 { | |
| 60 // Do not Json null values until it's an array | |
| 61 // | |
| 62 if (value == null || (value is XmlNode && IsEmptyNode((XmlNode)value))) | |
| 63 return; | |
| 64 | |
| 65 if (_first) | |
| 66 _first = false; | |
| 67 else | |
| 68 _sb | |
| 69 .Append(',') | |
| 70 .AppendLine() | |
| 71 ; | |
| 72 | |
| 73 for (int i = 0; i < _indent; ++i) | |
| 74 _sb.Append(' '); | |
| 75 | |
| 76 _sb | |
| 77 .Append('"') | |
| 78 .Append(name) | |
| 79 .Append("\":") | |
| 80 ; | |
| 81 } | |
| 82 | |
| 83 if (value == null) | |
| 84 _sb.Append("null"); | |
| 85 else | |
| 86 { | |
| 87 switch (Type.GetTypeCode(value.GetType())) | |
| 88 { | |
| 89 case TypeCode.Empty: | |
| 90 case TypeCode.DBNull: | |
| 91 _sb.Append("null"); | |
| 92 break; | |
| 93 case TypeCode.Boolean: | |
| 94 _sb.Append((bool)value? "true": "false"); | |
| 95 break; | |
| 96 case TypeCode.Char: | |
| 97 _sb | |
| 98 .Append('\'') | |
| 99 .Append((char)value) | |
| 100 .Append('\'') | |
| 101 ; | |
| 102 break; | |
| 103 case TypeCode.SByte: | |
| 104 case TypeCode.Int16: | |
| 105 case TypeCode.Int32: | |
| 106 case TypeCode.Int64: | |
| 107 case TypeCode.Byte: | |
| 108 case TypeCode.UInt16: | |
| 109 case TypeCode.UInt32: | |
| 110 case TypeCode.UInt64: | |
| 111 case TypeCode.Single: | |
| 112 case TypeCode.Double: | |
| 113 case TypeCode.Decimal: | |
| 114 _sb.Append(((IFormattable)value).ToString(null, CultureInfo.InvariantCulture)); | |
| 115 break; | |
| 116 case TypeCode.DateTime: | |
| 117 _sb | |
| 118 .Append("new Date(") | |
| 119 .Append((((DateTime)value).Ticks - InitialJavaScriptDateTicks)/10000) | |
| 120 .Append(")"); | |
| 121 break; | |
| 122 case TypeCode.String: | |
| 123 _sb | |
| 124 .Append('"') | |
| 125 .Append(encode((string)value)) | |
| 126 .Append('"') | |
| 127 ; | |
| 128 break; | |
| 129 default: | |
| 130 if (value is XmlNode) | |
| 131 { | |
| 132 if (IsEmptyNode((XmlNode) value)) | |
| 133 _sb.Append("null"); | |
| 134 else | |
| 135 WriteXmlJson((XmlNode)value); | |
| 136 } | |
| 137 else | |
| 138 { | |
| 139 JsonMapper inner = new JsonMapper(_sb, _indent + 1); | |
| 140 | |
| 141 if (value.GetType().IsArray) | |
| 142 _mappingSchema.MapSourceListToDestinationList( | |
| 143 _mappingSchema.GetDataSourceList(value), inner); | |
| 144 else | |
| 145 _mappingSchema.MapSourceToDestination( | |
| 146 _mappingSchema.GetDataSource(value), value, inner, inner); | |
| 147 } | |
| 148 break; | |
| 149 } | |
| 150 } | |
| 151 } | |
| 152 | |
| 153 private static string encode(string value) | |
| 154 { | |
| 155 return value.Replace("\r\n", "\\r") | |
| 156 .Replace("\n\r", "\\r") | |
| 157 .Replace("\n", "\\r") | |
| 158 .Replace("\r", "\\r") | |
| 159 .Replace("\"","\\\""); | |
| 160 } | |
| 161 | |
| 162 private void WriteXmlJson(XmlNode node) | |
| 163 { | |
| 164 XmlNode textNode = GetTextNode(node); | |
| 165 if (textNode != null) | |
| 166 { | |
| 167 _sb | |
| 168 .Append("\"") | |
| 169 .Append(encode(textNode.Value)) | |
| 170 .Append('\"') | |
| 171 ; | |
| 172 } | |
| 173 else | |
| 174 { | |
| 175 | |
| 176 bool first = true; | |
| 177 | |
| 178 _sb.Append('{'); | |
| 179 | |
| 180 if (node.Attributes != null) | |
| 181 { | |
| 182 foreach (XmlAttribute attr in node.Attributes) | |
| 183 { | |
| 184 if (first) | |
| 185 first = false; | |
| 186 else | |
| 187 _sb.Append(','); | |
| 188 | |
| 189 _sb | |
| 190 .Append("\"@") | |
| 191 .Append(attr.Name) | |
| 192 .Append("\":\"") | |
| 193 .Append(encode(attr.Value)) | |
| 194 .Append('\"') | |
| 195 ; | |
| 196 } | |
| 197 } | |
| 198 | |
| 199 foreach (XmlNode child in node.ChildNodes) | |
| 200 { | |
| 201 if (IsWhitespace(child) || IsEmptyNode(child)) | |
| 202 continue; | |
| 203 | |
| 204 if (first) | |
| 205 first = false; | |
| 206 else | |
| 207 _sb.Append(','); | |
| 208 | |
| 209 if (child is XmlText) | |
| 210 _sb | |
| 211 .Append("\"#text\":\"") | |
| 212 .Append(encode(child.Value)) | |
| 213 .Append('\"') | |
| 214 ; | |
| 215 else if (child is XmlElement) | |
| 216 { | |
| 217 _sb | |
| 218 .Append('"') | |
| 219 .Append(child.Name) | |
| 220 .Append("\":") | |
| 221 ; | |
| 222 WriteXmlJson(child); | |
| 223 } | |
| 224 else | |
| 225 System.Diagnostics.Debug.Fail("Unexpected node type " + child.GetType().FullName); | |
| 226 } | |
| 227 _sb.Append('}'); | |
| 228 } | |
| 229 } | |
| 230 | |
| 231 private static bool IsWhitespace(XmlNode node) | |
| 232 { | |
| 233 switch (node.NodeType) | |
| 234 { | |
| 235 case XmlNodeType.Comment: | |
| 236 case XmlNodeType.Whitespace: | |
| 237 case XmlNodeType.SignificantWhitespace: | |
| 238 return true; | |
| 239 } | |
| 240 return false; | |
| 241 } | |
| 242 | |
| 243 private static bool IsEmptyNode(XmlNode node) | |
| 244 { | |
| 245 if (node.Attributes != null && node.Attributes.Count > 0) | |
| 246 return false; | |
| 247 | |
| 248 if (node.HasChildNodes) | |
| 249 foreach (XmlNode childNode in node.ChildNodes) | |
| 250 { | |
| 251 if (IsWhitespace(childNode) || IsEmptyNode(childNode)) | |
| 252 continue; | |
| 253 | |
| 254 // Not a whitespace, nor inner empty node. | |
| 255 // | |
| 256 return false; | |
| 257 } | |
| 258 | |
| 259 return node.Value == null; | |
| 260 } | |
| 261 | |
| 262 private static XmlNode GetTextNode(XmlNode node) | |
| 263 { | |
| 264 if (node.Attributes != null && node.Attributes.Count > 0) | |
| 265 return null; | |
| 266 | |
| 267 XmlNode textNode = null; | |
| 268 | |
| 269 foreach (XmlNode childNode in node.ChildNodes) | |
| 270 { | |
| 271 // Ignore all whitespace. | |
| 272 // | |
| 273 if (IsWhitespace(childNode)) | |
| 274 continue; | |
| 275 | |
| 276 if (childNode is XmlText) | |
| 277 { | |
| 278 // More then one text node. | |
| 279 // | |
| 280 if (textNode != null) | |
| 281 return null; | |
| 282 | |
| 283 // First text node. | |
| 284 // | |
| 285 textNode = childNode; | |
| 286 } | |
| 287 else | |
| 288 // Not a text node - break; | |
| 289 // | |
| 290 return null; | |
| 291 } | |
| 292 | |
| 293 return textNode; | |
| 294 } | |
| 295 | |
| 296 #region ISupportMapping Members | |
| 297 | |
| 298 void ISupportMapping.BeginMapping(InitContext initContext) | |
| 299 { | |
| 300 _first = true; | |
| 301 _mappingSchema = initContext.MappingSchema; | |
| 302 _fieldNames = new string[initContext.DataSource.Count]; | |
| 303 | |
| 304 for (int i = 0; i < _fieldNames.Length; ++i) | |
| 305 _fieldNames[i] = initContext.DataSource.GetName(i); | |
| 306 | |
| 307 _scalar = _fieldNames.Length == 1 && string.IsNullOrEmpty(_fieldNames[0]); | |
| 308 | |
| 309 if (_scalar) | |
| 310 return; | |
| 311 | |
| 312 if (_fieldNames.Length <= 1) | |
| 313 { | |
| 314 // Reset the indent since output is a single line. | |
| 315 // | |
| 316 _indent = 0; | |
| 317 _sb.Append('{'); | |
| 318 } | |
| 319 else | |
| 320 { | |
| 321 if (_indent > 0) | |
| 322 _sb.AppendLine(); | |
| 323 | |
| 324 for (int i = 0; i < _indent; ++i) | |
| 325 _sb.Append(' '); | |
| 326 | |
| 327 _sb | |
| 328 .Append('{') | |
| 329 .AppendLine() | |
| 330 ; | |
| 331 } | |
| 332 } | |
| 333 | |
| 334 void ISupportMapping.EndMapping(InitContext initContext) | |
| 335 { | |
| 336 if (_scalar) | |
| 337 return; | |
| 338 | |
| 339 if (_fieldNames.Length > 1) | |
| 340 _sb.AppendLine(); | |
| 341 | |
| 342 for (int i = 0; i < _indent; ++i) | |
| 343 _sb.Append(' '); | |
| 344 _sb.Append('}'); | |
| 345 } | |
| 346 | |
| 347 #endregion | |
| 348 | |
| 349 #region IMapDataDestinationList Members | |
| 350 | |
| 351 void IMapDataDestinationList.InitMapping(InitContext initContext) | |
| 352 { | |
| 353 _firstElement = true; | |
| 354 _sb.Append('['); | |
| 355 } | |
| 356 | |
| 357 IMapDataDestination IMapDataDestinationList.GetDataDestination(InitContext initContext) | |
| 358 { | |
| 359 return this; | |
| 360 } | |
| 361 | |
| 362 object IMapDataDestinationList.GetNextObject(InitContext initContext) | |
| 363 { | |
| 364 if (_firstElement) | |
| 365 _firstElement = false; | |
| 366 else | |
| 367 _sb.Append(','); | |
| 368 | |
| 369 return this; | |
| 370 } | |
| 371 | |
| 372 void IMapDataDestinationList.EndMapping(InitContext initContext) | |
| 373 { | |
| 374 _sb.Append(']'); | |
| 375 } | |
| 376 | |
| 377 #endregion | |
| 378 | |
| 379 public override string ToString() | |
| 380 { | |
| 381 return _sb.ToString(); | |
| 382 } | |
| 383 } | |
| 384 | |
| 385 [TestFixture] | |
| 386 public class MapToJson | |
| 387 { | |
| 388 public class Inner | |
| 389 { | |
| 390 public string Name = "inner \"object \n name"; | |
| 391 } | |
| 392 | |
| 393 public class Inner2 | |
| 394 { | |
| 395 public string Name; | |
| 396 public int Value; | |
| 397 } | |
| 398 | |
| 399 public class SourceObject | |
| 400 { | |
| 401 public string Foo = "Foo"; | |
| 402 public double Bar = 1.23; | |
| 403 public DateTime Baz = DateTime.Today; | |
| 404 [MapIgnore(false)] | |
| 405 public Inner Inner = new Inner(); | |
| 406 [MapIgnore(false)] | |
| 407 public Inner2 Inner2 = new Inner2(); | |
| 408 public string[] StrArray = {"One", "Two", "Three"}; | |
| 409 } | |
| 410 | |
| 411 [Test] | |
| 412 public void Test() | |
| 413 { | |
| 414 JsonMapper jm = new JsonMapper(new StringBuilder(256)); | |
| 415 | |
| 416 Map./*[a]*/MapSourceToDestination/*[/a]*/(Map.GetObjectMapper(typeof(SourceObject)), new SourceObject(), jm, jm); | |
| 417 Console.Write(jm.ToString()); | |
| 418 | |
| 419 // Expected output: | |
| 420 // | |
| 421 // { | |
| 422 // "Foo":"Foo", | |
| 423 // "Bar":1.23, | |
| 424 // "Baz":new Date(11823840000000000), | |
| 425 // "Inner":{ "Name":"inner \"object \r name"}, | |
| 426 // "Inner2": | |
| 427 // { | |
| 428 // "Name":null, | |
| 429 // "Value":0 | |
| 430 // }, | |
| 431 // "StrArray":["One","Two","Three"] | |
| 432 // } | |
| 433 } | |
| 434 } | |
| 435 } |
