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 } |