source: other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/JsonSerializationExtensions.cs@ 28897

Last change on this file since 28897 was 28897, checked in by davidb, 10 years ago

GUI front-end to server base plus web page content

File size: 22.6 KB
Line 
1// -----------------------------------------------------------------------
2// <copyright file="JsonSerializationExtensions.cs" company="Microsoft">
3// Copyright (c) Microsoft Corporation. All rights reserved.
4// </copyright>
5// -----------------------------------------------------------------------
6
7namespace Microsoft.Samples.Kinect.Webserver
8{
9 using System;
10 using System.Collections;
11 using System.Collections.Generic;
12 using System.Globalization;
13 using System.IO;
14 using System.Reflection;
15 using System.Runtime.Serialization;
16 using System.Text;
17 using System.Threading.Tasks;
18 using System.Web.Script.Serialization;
19
20 using Microsoft.Samples.Kinect.Webserver.Properties;
21
22 /// <summary>
23 /// Static class that defines extensions used to serialize/deserialize objects to/from
24 /// JSON strings.
25 /// </summary>
26 public static class JsonSerializationExtensions
27 {
28 /// <summary>
29 /// Serialization default buffer size.
30 /// </summary>
31 private const int BufferSize = 512;
32
33 /// <summary>
34 /// Serialize specified object to a stream as UTF8-encoded JSON.
35 /// </summary>
36 /// <typeparam name="T">
37 /// Type of object to serialize.
38 /// </typeparam>
39 /// <param name="obj">
40 /// Object to serialize.
41 /// </param>
42 /// <param name="outputStream">
43 /// Stream where UTF8-encoded JSON representing object will be output.
44 /// </param>
45 public static void ToJson<T>(this T obj, Stream outputStream)
46 {
47 using (var writer = new StreamWriter(outputStream, new UTF8Encoding(false), BufferSize, true))
48 {
49 writer.Write(obj.ToJson());
50 }
51 }
52
53 /// <summary>
54 /// Serialize specified object to a JSON string.
55 /// </summary>
56 /// <typeparam name="T">
57 /// Type of object to serialize.
58 /// </typeparam>
59 /// <param name="obj">
60 /// Object to serialize.
61 /// </param>
62 /// <returns>
63 /// JSON string representing serialized object.
64 /// </returns>
65 public static string ToJson<T>(this T obj)
66 {
67 return (new JavaScriptSerializer()).Serialize(obj);
68 }
69
70 /// <summary>
71 /// Serialize specified dictionary to a stream as UTF8-encoded JSON.
72 /// </summary>
73 /// <param name="dictionary">
74 /// Dictionary to serialize.
75 /// </param>
76 /// <param name="outputStream">
77 /// Stream where UTF8-encoded JSON representing dictionary will be output.
78 /// </param>
79 /// <remarks>
80 /// <para>
81 /// Only IDictionary&lt;string,object&gt; objects and default types will be
82 /// recognized by the serializer.
83 /// </para>
84 /// <para>
85 /// Dictionaries mapping string keys to object values will be treated as direct
86 /// representations of JSON objects, so that a dictionary that contains:
87 /// <list type="bullet">
88 /// <item>
89 /// <description>a key "foo" that maps to a numeric value of 23</description>
90 /// </item>
91 /// <item>
92 /// <description>a key "bar" that maps to a string value of "tar"</description>
93 /// </item>
94 /// </list>
95 /// will be serialized as {"foo":23,"bar":"tar"}, rather than as
96 /// [{"Key":"foo","Value":23},{"Key":"bar","Value":"tar"}], which is the default
97 /// way to serialize dictionary objects as JSON.
98 /// </para>
99 /// <para>
100 /// This method does not look for circular references and therefore does not
101 /// support them.
102 /// </para>
103 /// </remarks>
104 public static void DictionaryToJson(this IDictionary<string, object> dictionary, Stream outputStream)
105 {
106 // Leave output stream open after we're done writing
107 using (var writer = new StreamWriter(outputStream, new UTF8Encoding(false), BufferSize, true))
108 {
109 writer.Write(dictionary.DictionaryToJson());
110 }
111 }
112
113 /// <summary>
114 /// Asynchronously serialize specified dictionary to a stream as UTF8-encoded JSON.
115 /// </summary>
116 /// <param name="dictionary">
117 /// Dictionary to serialize.
118 /// </param>
119 /// <param name="outputStream">
120 /// Stream where UTF8-encoded JSON representing dictionary will be output.
121 /// </param>
122 /// <returns>
123 /// Await-able task.
124 /// </returns>
125 /// <remarks>
126 /// <para>
127 /// Only IDictionary&lt;string,object&gt; objects and default types will be
128 /// recognized by the serializer.
129 /// </para>
130 /// <para>
131 /// Dictionaries mapping string keys to object values will be treated as direct
132 /// representations of JSON objects, so that a dictionary that contains:
133 /// <list type="bullet">
134 /// <item>
135 /// <description>a key "foo" that maps to a numeric value of 23</description>
136 /// </item>
137 /// <item>
138 /// <description>a key "bar" that maps to a string value of "tar"</description>
139 /// </item>
140 /// </list>
141 /// will be serialized as {"foo":23,"bar":"tar"}, rather than as
142 /// [{"Key":"foo","Value":23},{"Key":"bar","Value":"tar"}], which is the default
143 /// way to serialize dictionary objects as JSON.
144 /// </para>
145 /// <para>
146 /// This method does not look for circular references and therefore does not
147 /// support them.
148 /// </para>
149 /// </remarks>
150 public static async Task DictionaryToJsonAsync(this IDictionary<string, object> dictionary, Stream outputStream)
151 {
152 // Leave output stream open after we're done writing
153 using (var writer = new StreamWriter(outputStream, new UTF8Encoding(false), BufferSize, true))
154 {
155 await writer.WriteAsync(dictionary.DictionaryToJson());
156 }
157 }
158
159 /// <summary>
160 /// Serialize specified dictionary to a JSON string.
161 /// </summary>
162 /// <param name="dictionary">
163 /// Dictionary to serialize.
164 /// </param>
165 /// <returns>
166 /// JSON string representing serialized object.
167 /// </returns>
168 /// <remarks>
169 /// <para>
170 /// Only IDictionary&lt;string,object&gt; objects and default types will be
171 /// recognized by the serializer.
172 /// </para>
173 /// <para>
174 /// Dictionaries mapping string keys to object values will be treated as direct
175 /// representations of JSON objects, so that a dictionary that contains:
176 /// <list type="bullet">
177 /// <item>
178 /// <description>a key "foo" that maps to a numeric value of 23</description>
179 /// </item>
180 /// <item>
181 /// <description>a key "bar" that maps to a string value of "tar"</description>
182 /// </item>
183 /// </list>
184 /// will be serialized as {"foo":23,"bar":"tar"}, rather than as
185 /// [{"Key":"foo","Value":23},{"Key":"bar","Value":"tar"}], which is the default
186 /// way to serialize dictionary objects as JSON.
187 /// </para>
188 /// <para>
189 /// This method does not look for circular references and therefore does not
190 /// support them.
191 /// </para>
192 /// </remarks>
193 public static string DictionaryToJson(this IDictionary<string, object> dictionary)
194 {
195 return (new JavaScriptSerializer()).Serialize(dictionary);
196 }
197
198 /// <summary>
199 /// Deserialize specified object from UTF8-encoded JSON read from a stream.
200 /// </summary>
201 /// <typeparam name="T">
202 /// Type of object to deserialize.
203 /// </typeparam>
204 /// <param name="inputStream">
205 /// Stream from which to read UTF8-encoded JSON representing serialized object.
206 /// </param>
207 /// <returns>
208 /// Deserialized object corresponding to input JSON.
209 /// </returns>
210 public static T FromJson<T>(this Stream inputStream)
211 {
212 using (var reader = new StreamReader(inputStream, Encoding.UTF8))
213 {
214 return reader.ReadToEnd().FromJson<T>();
215 }
216 }
217
218 /// <summary>
219 /// Deserialize specified object from a JSON string.
220 /// </summary>
221 /// <typeparam name="T">
222 /// Type of object to deserialize.
223 /// </typeparam>
224 /// <param name="input">
225 /// JSON string representing serialized object.
226 /// </param>
227 /// <returns>
228 /// Deserialized object corresponding to JSON string.
229 /// </returns>
230 /// <remarks>
231 /// Errors encountered during serialization might throw SerializationException.
232 /// </remarks>
233 public static T FromJson<T>(this string input)
234 {
235 try
236 {
237 return (new JavaScriptSerializer()).Deserialize<T>(input);
238
239 // Convert exceptions to Serialization exception to provide a single exception to
240 // catch for callers.
241 }
242 catch (ArgumentException e)
243 {
244 throw new SerializationException(@"Exception encountered deserializing JSON string", e);
245 }
246 catch (InvalidOperationException e)
247 {
248 throw new SerializationException(@"Exception encountered deserializing JSON string", e);
249 }
250 }
251
252 /// <summary>
253 /// Deserialize specified dictionary from a stream as UTF8-encoded JSON.
254 /// </summary>
255 /// <param name="inputStream">
256 /// Stream containing UTF8-encoded JSON representation of dictionary.
257 /// </param>
258 /// <returns>
259 /// Deserialized dictionary.
260 /// </returns>
261 /// <remarks>
262 /// <para>
263 /// Dictionaries mapping string keys to object values will be treated as direct
264 /// representations of JSON objects, so that a JSON object such as
265 /// {"foo":23,"bar":"tar"} will be deserialized as a dictionary that contains:
266 /// <list type="bullet">
267 /// <item>
268 /// <description>a key "foo" that maps to a numeric value of 23</description>
269 /// </item>
270 /// <item>
271 /// <description>a key "bar" that maps to a string value of "tar"</description>
272 /// </item>
273 /// </list>
274 /// </para>
275 /// <para>
276 /// This method does not look for circular references and therefore does not
277 /// support them.
278 /// </para>
279 /// </remarks>
280 public static Dictionary<string, object> DictionaryFromJson(this Stream inputStream)
281 {
282 using (var reader = new StreamReader(inputStream, Encoding.UTF8))
283 {
284 return reader.ReadToEnd().DictionaryFromJson();
285 }
286 }
287
288 /// <summary>
289 /// Asynchronously deserialize specified dictionary from a stream as UTF8-encoded JSON.
290 /// </summary>
291 /// <param name="inputStream">
292 /// Stream containing UTF8-encoded JSON representation of dictionary.
293 /// </param>
294 /// <returns>
295 /// Await-able task Deserialized dictionary.
296 /// </returns>
297 /// <remarks>
298 /// <para>
299 /// Dictionaries mapping string keys to object values will be treated as direct
300 /// representations of JSON objects, so that a JSON object such as
301 /// {"foo":23,"bar":"tar"} will be deserialized as a dictionary that contains:
302 /// <list type="bullet">
303 /// <item>
304 /// <description>a key "foo" that maps to a numeric value of 23</description>
305 /// </item>
306 /// <item>
307 /// <description>a key "bar" that maps to a string value of "tar"</description>
308 /// </item>
309 /// </list>
310 /// </para>
311 /// <para>
312 /// This method does not look for circular references and therefore does not
313 /// support them.
314 /// </para>
315 /// </remarks>
316 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Clients won't have to create nested structure themselves. They will just await on task to get dictionary object.")]
317 public static async Task<Dictionary<string, object>> DictionaryFromJsonAsync(this Stream inputStream)
318 {
319 using (var reader = new StreamReader(inputStream, Encoding.UTF8))
320 {
321 var input = await reader.ReadToEndAsync();
322 return input.DictionaryFromJson();
323 }
324 }
325
326 /// <summary>
327 /// Deserialize specified dictionary from a JSON string.
328 /// </summary>
329 /// <param name="input">
330 /// JSON string containing JSON representation of dictionary.
331 /// </param>
332 /// <returns>
333 /// Deserialized dictionary.
334 /// </returns>
335 /// <remarks>
336 /// <para>
337 /// Dictionaries mapping string keys to object values will be treated as direct
338 /// representations of JSON objects, so that a JSON object such as
339 /// {"foo":23,"bar":"tar"} will be deserialized as a dictionary that contains:
340 /// <list type="bullet">
341 /// <item>
342 /// <description>a key "foo" that maps to a numeric value of 23</description>
343 /// </item>
344 /// <item>
345 /// <description>a key "bar" that maps to a string value of "tar"</description>
346 /// </item>
347 /// </list>
348 /// </para>
349 /// <para>
350 /// This method does not look for circular references and therefore does not
351 /// support them.
352 /// </para>
353 /// <para>
354 /// Errors encountered during serialization might throw SerializationException.
355 /// </para>
356 /// </remarks>
357 public static Dictionary<string, object> DictionaryFromJson(this string input)
358 {
359 try
360 {
361 return (new JavaScriptSerializer()).Deserialize<Dictionary<string, object>>(input);
362
363 // Convert exceptions to Serialization exception to provide a single exception to
364 // catch for callers.
365 }
366 catch (ArgumentException e)
367 {
368 throw new SerializationException(@"Exception encountered deserializing JSON string", e);
369 }
370 catch (InvalidOperationException e)
371 {
372 throw new SerializationException(@"Exception encountered deserializing JSON string", e);
373 }
374 }
375
376 /// <summary>
377 /// Extract serializable JSON data from specified value, ensuring that property names
378 /// match JSON naming conventions (camel case).
379 /// </summary>
380 /// <param name="value">The object to be converted.</param>
381 /// <returns>The converted object.</returns>
382 internal static object ExtractSerializableJsonData(object value)
383 {
384 if (value == null || IsSerializationPrimitive(value))
385 {
386 return value;
387 }
388
389 if (IsDictionary(value))
390 {
391 // The key type for the given dictionary must be string type.
392 if (!IsSerializableGenericDictionary(value) && !IsSerializableDictionary(value))
393 {
394 throw new NotSupportedException(Resources.UnsupportedKeyType);
395 }
396
397 var result = new Dictionary<string, object>();
398 var dict = (IDictionary)value;
399
400 foreach (var key in dict.Keys)
401 {
402 result.Add((string)key, ExtractSerializableJsonData(dict[key]));
403 }
404
405 return result;
406 }
407
408 if (IsEnumerable(value))
409 {
410 // For the object with IEnumerable interface, serialize each items in it.
411 var result = new List<object>();
412
413 foreach (var v in (IEnumerable)value)
414 {
415 result.Add(ExtractSerializableJsonData(v));
416 }
417
418 return result;
419 }
420 else
421 {
422 var result = new Dictionary<string, object>();
423
424 foreach (var p in value.GetType().GetProperties())
425 {
426 if (IsSerializableProperty(p))
427 {
428 result.Add(ToCamelCase(p.Name), ExtractSerializableJsonData(p.GetValue(value)));
429 }
430 }
431
432 return result;
433 }
434 }
435
436 /// <summary>
437 /// Filters the classes represented in an array of Type objects.
438 /// </summary>
439 /// <param name="type">The Type object to which the filter is applied.</param>
440 /// <param name="criteria">An arbitrary object used to filter the list.</param>
441 /// <returns>True to include the Type in the filtered list; otherwise false.</returns>
442 private static bool InterfaceTypeFilter(Type type, object criteria)
443 {
444 var targetType = criteria as Type;
445
446 if (type.IsGenericType)
447 {
448 return type.GetGenericTypeDefinition() == targetType;
449 }
450
451 return targetType != null && type == targetType;
452 }
453
454 /// <summary>
455 /// Find the target interface from a given object.
456 /// </summary>
457 /// <param name="value">The given object.</param>
458 /// <param name="targetInterface">The target interface to be found.</param>
459 /// <returns>The interface which has been found.</returns>
460 private static Type FindTargetInterface(object value, Type targetInterface)
461 {
462 var types = value.GetType().FindInterfaces(InterfaceTypeFilter, targetInterface);
463 return (types.Length > 0) ? types[0] : null;
464 }
465
466 /// <summary>
467 /// Determine if the given object has a serializable generic dictionary interface.
468 /// </summary>
469 /// <param name="value">The given object.</param>
470 /// <returns>Returns true if it has the interface; otherwise false.</returns>
471 private static bool IsSerializableGenericDictionary(object value)
472 {
473 var genericDictType = typeof(IDictionary<object, object>).GetGenericTypeDefinition();
474 var type = FindTargetInterface(value, genericDictType);
475
476 if (type != null)
477 {
478 // Only the dictionaries with string type keys are serializable.
479 var argumentTypes = type.GetGenericArguments();
480 return argumentTypes.Length > 0 && argumentTypes[0] == typeof(string);
481 }
482
483 return false;
484 }
485
486 /// <summary>
487 /// Determine if the given object has a serializable dictionary interface.
488 /// </summary>
489 /// <param name="value">The given object.</param>
490 /// <returns>Returns true if it has the interface; otherwise false.</returns>
491 private static bool IsSerializableDictionary(object value)
492 {
493 var type = FindTargetInterface(value, typeof(IDictionary));
494
495 if (type == null)
496 {
497 return false;
498 }
499
500 foreach (var key in ((IDictionary)value).Keys)
501 {
502 if (!(key is string))
503 {
504 return false;
505 }
506 }
507
508 return true;
509 }
510
511 /// <summary>
512 /// Determine if the given object has a dictionary or generic dictionary interface.
513 /// </summary>
514 /// <param name="value">The given object.</param>
515 /// <returns>Returns true if it has the interface; otherwise false.</returns>
516 private static bool IsDictionary(object value)
517 {
518 var dictType = FindTargetInterface(value, typeof(IDictionary));
519 if (dictType == null)
520 {
521 dictType = FindTargetInterface(value, typeof(IDictionary<object, object>).GetGenericTypeDefinition());
522 }
523
524 return dictType != null;
525 }
526
527 /// <summary>
528 /// Determine if the given object has an IEnumerable interface.
529 /// </summary>
530 /// <param name="value">The given object.</param>
531 /// <returns>Returns true if it has the interface; otherwise false.</returns>
532 private static bool IsEnumerable(object value)
533 {
534 var type = FindTargetInterface(value, typeof(IEnumerable));
535 return type != null;
536 }
537
538 /// <summary>
539 /// Convert the given string to camelCase.
540 /// </summary>
541 /// <param name="text">The given string.</param>
542 /// <returns>The returned string.</returns>
543 private static string ToCamelCase(string text)
544 {
545 if (string.IsNullOrEmpty(text))
546 {
547 return text;
548 }
549
550 return string.Format(CultureInfo.CurrentCulture, "{0}{1}", char.ToLower(text[0], CultureInfo.CurrentCulture), text.Substring(1));
551 }
552
553 /// <summary>
554 /// Determine if the given object is a primitive to be serialized.
555 /// </summary>
556 /// <param name="o">The input object.</param>
557 /// <returns>Return true if it is a serialization primitive, otherwise return false.</returns>
558 private static bool IsSerializationPrimitive(object o)
559 {
560 Type t = o.GetType();
561 return t.IsPrimitive || t.IsEnum || t == typeof(string) || t == typeof(DateTime);
562 }
563
564 /// <summary>
565 /// Determine if the given property is serializable.
566 /// </summary>
567 /// <param name="propertyInfo">The input property.</param>
568 /// <returns>Return true if it is serializable, otherwise return false.</returns>
569 private static bool IsSerializableProperty(PropertyInfo propertyInfo)
570 {
571 foreach (var accessor in propertyInfo.GetAccessors())
572 {
573 if (accessor.IsPublic && !accessor.IsStatic)
574 {
575 return propertyInfo.GetGetMethod(false) != null && propertyInfo.CanWrite;
576 }
577 }
578
579 return false;
580 }
581 }
582}
Note: See TracBrowser for help on using the repository browser.