Merge pull request #487 from mayerwin/patch-1
[mono.git] / mcs / class / System.Json.Microsoft / System.Json / Extensions / JsonValueExtensions.cs
1 // Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
2 #if FEATURE_DYNAMIC
3 using System.Collections.Generic;
4 using System.ComponentModel;
5 using System.Diagnostics.CodeAnalysis;
6 using System.Dynamic;
7 using System.IO;
8 using System.Json;
9 using System.Linq.Expressions;
10
11 namespace System.Runtime.Serialization.Json
12 {
13     /// <summary>
14     /// This class extends the functionality of the <see cref="JsonValue"/> type. 
15     /// </summary>
16     [EditorBrowsable(EditorBrowsableState.Never)]
17     public static class JsonValueExtensions
18     {
19         /// <summary>
20         /// Creates a <see cref="System.Json.JsonValue"/> object based on an arbitrary CLR object.
21         /// </summary>
22         /// <param name="value">The object to be converted to <see cref="System.Json.JsonValue"/>.</param>
23         /// <returns>The <see cref="System.Json.JsonValue"/> which represents the given object.</returns>
24         /// <remarks>The conversion is done through the <see cref="System.Runtime.Serialization.Json.DataContractJsonSerializer"/>;
25         /// the object is first serialized into JSON using the serializer, then parsed into a <see cref="System.Json.JsonValue"/>
26         /// object.</remarks>
27         public static JsonValue CreateFrom(object value)
28         {
29             JsonValue jsonValue = null;
30
31             if (value != null)
32             {
33                 jsonValue = value as JsonValue;
34
35                 if (jsonValue == null)
36                 {
37                     jsonValue = JsonValueExtensions.CreatePrimitive(value);
38
39                     if (jsonValue == null)
40                     {
41                         jsonValue = JsonValueExtensions.CreateFromDynamic(value);
42
43                         if (jsonValue == null)
44                         {
45                             jsonValue = JsonValueExtensions.CreateFromComplex(value);
46                         }
47                     }
48                 }
49             }
50
51             return jsonValue;
52         }
53
54         /// <summary>
55         /// Attempts to convert this <see cref="System.Json.JsonValue"/> instance into the type T.
56         /// </summary>
57         /// <typeparam name="T">The type to which the conversion is being performed.</typeparam>
58         /// <param name="jsonValue">The <see cref="JsonValue"/> instance this method extension is to be applied to.</param>
59         /// <param name="valueOfT">An instance of T initialized with this instance, or the default
60         /// value of T, if the conversion cannot be performed.</param>
61         /// <returns>true if this <see cref="System.Json.JsonValue"/> instance can be read as type T; otherwise, false.</returns>
62         public static bool TryReadAsType<T>(this JsonValue jsonValue, out T valueOfT)
63         {
64             if (jsonValue == null)
65             {
66                 throw new ArgumentNullException("jsonValue");
67             }
68
69             object value;
70             if (JsonValueExtensions.TryReadAsType(jsonValue, typeof(T), out value))
71             {
72                 valueOfT = (T)value;
73                 return true;
74             }
75
76             valueOfT = default(T);
77             return false;
78         }
79
80         /// <summary>
81         /// Attempts to convert this <see cref="System.Json.JsonValue"/> instance into the type T.
82         /// </summary>
83         /// <typeparam name="T">The type to which the conversion is being performed.</typeparam>
84         /// <param name="jsonValue">The <see cref="JsonValue"/> instance this method extension is to be applied to.</param>
85         /// <returns>An instance of T initialized with the <see cref="System.Json.JsonValue"/> value
86         /// specified if the conversion.</returns>
87         /// <exception cref="System.NotSupportedException">If this <see cref="System.Json.JsonValue"/> value cannot be
88         /// converted into the type T.</exception>
89         [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter",
90             Justification = "The generic parameter is used to specify the output type")]
91         public static T ReadAsType<T>(this JsonValue jsonValue)
92         {
93             if (jsonValue == null)
94             {
95                 throw new ArgumentNullException("jsonValue");
96             }
97
98             return (T)JsonValueExtensions.ReadAsType(jsonValue, typeof(T));
99         }
100
101         /// <summary>
102         /// Attempts to convert this <see cref="System.Json.JsonValue"/> instance into the type T, returning a fallback value
103         /// if the conversion fails.
104         /// </summary>
105         /// <typeparam name="T">The type to which the conversion is being performed.</typeparam>
106         /// <param name="jsonValue">The <see cref="JsonValue"/> instance this method extension is to be applied to.</param>
107         /// <param name="fallback">A fallback value to be retuned in case the conversion cannot be performed.</param>
108         /// <returns>An instance of T initialized with the <see cref="System.Json.JsonValue"/> value
109         /// specified if the conversion succeeds or the specified fallback value if it fails.</returns>
110         public static T ReadAsType<T>(this JsonValue jsonValue, T fallback)
111         {
112             if (jsonValue == null)
113             {
114                 throw new ArgumentNullException("jsonValue");
115             }
116
117             T outVal;
118             if (JsonValueExtensions.TryReadAsType<T>(jsonValue, out outVal))
119             {
120                 return outVal;
121             }
122
123             return fallback;
124         }
125
126         /// <summary>
127         /// Attempts to convert this <see cref="System.Json.JsonValue"/> instance into an instance of the specified type.
128         /// </summary>
129         /// <param name="jsonValue">The <see cref="JsonValue"/> instance this method extension is to be applied to.</param>
130         /// <param name="type">The type to which the conversion is being performed.</param>
131         /// <returns>An object instance initialized with the <see cref="System.Json.JsonValue"/> value
132         /// specified if the conversion.</returns>
133         /// <exception cref="System.NotSupportedException">If this <see cref="System.Json.JsonValue"/> value cannot be
134         /// converted into the type T.</exception>
135         public static object ReadAsType(this JsonValue jsonValue, Type type)
136         {
137             if (jsonValue == null)
138             {
139                 throw new ArgumentNullException("jsonValue");
140             }
141
142             if (type == null)
143             {
144                 throw new ArgumentNullException("type");
145             }
146
147             object result;
148             if (JsonValueExtensions.TryReadAsType(jsonValue, type, out result))
149             {
150                 return result;
151             }
152
153             throw new NotSupportedException(RS.Format(System.Json.Properties.Resources.CannotReadAsType, jsonValue.GetType().FullName, type.FullName));
154         }
155
156         /// <summary>
157         /// Attempts to convert this <see cref="System.Json.JsonValue"/> instance into an instance of the specified type.
158         /// </summary>
159         /// <param name="jsonValue">The <see cref="JsonValue"/> instance this method extension is to be applied to.</param>
160         /// <param name="type">The type to which the conversion is being performed.</param>
161         /// <param name="value">An object to be initialized with this instance or null if the conversion cannot be performed.</param>
162         /// <returns>true if this <see cref="System.Json.JsonValue"/> instance can be read as the specified type; otherwise, false.</returns>
163         [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate",
164             Justification = "This is the non-generic version of the method.")]
165         [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exception translates to fail.")]
166         public static bool TryReadAsType(this JsonValue jsonValue, Type type, out object value)
167         {
168             if (jsonValue == null)
169             {
170                 throw new ArgumentNullException("jsonValue");
171             }
172
173             if (type == null)
174             {
175                 throw new ArgumentNullException("type");
176             }
177
178             if (type == typeof(JsonValue) || type == typeof(object))
179             {
180                 value = jsonValue;
181                 return true;
182             }
183
184             if (type == typeof(object[]) || type == typeof(Dictionary<string, object>))
185             {
186                 if (!JsonValueExtensions.CanConvertToClrCollection(jsonValue, type))
187                 {
188                     value = null;
189                     return false;
190                 }
191             }
192
193             if (jsonValue.TryReadAs(type, out value))
194             {
195                 return true;
196             }
197
198             try
199             {
200                 using (MemoryStream ms = new MemoryStream())
201                 {
202                     jsonValue.Save(ms);
203                     ms.Position = 0;
204                     DataContractJsonSerializer dcjs = new DataContractJsonSerializer(type);
205                     value = dcjs.ReadObject(ms);
206                 }
207
208                 return true;
209             }
210             catch (Exception)
211             {
212                 value = null;
213                 return false;
214             }
215         }
216
217         /// <summary>
218         /// Determines whether the specified <see cref="JsonValue"/> instance can be converted to the specified collection <see cref="Type"/>.
219         /// </summary>
220         /// <param name="jsonValue">The instance to be converted.</param>
221         /// <param name="collectionType">The collection type to convert the instance to.</param>
222         /// <returns>true if the instance can be converted, false otherwise</returns>
223         private static bool CanConvertToClrCollection(JsonValue jsonValue, Type collectionType)
224         {
225             if (jsonValue != null)
226             {
227                 return (jsonValue.JsonType == JsonType.Object && collectionType == typeof(Dictionary<string, object>)) ||
228                        (jsonValue.JsonType == JsonType.Array && collectionType == typeof(object[]));
229             }
230
231             return false;
232         }
233
234         private static JsonValue CreatePrimitive(object value)
235         {
236             JsonPrimitive jsonPrimitive;
237
238             if (JsonPrimitive.TryCreate(value, out jsonPrimitive))
239             {
240                 return jsonPrimitive;
241             }
242
243             return null;
244         }
245
246         private static JsonValue CreateFromComplex(object value)
247         {
248             DataContractJsonSerializer dcjs = new DataContractJsonSerializer(value.GetType());
249             using (MemoryStream ms = new MemoryStream())
250             {
251                 dcjs.WriteObject(ms, value);
252                 ms.Position = 0;
253                 return JsonValue.Load(ms);
254             }
255         }
256
257         [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "value is not the same")]
258         private static JsonValue CreateFromDynamic(object value)
259         {
260             JsonObject parent = null;
261             DynamicObject dynObj = value as DynamicObject;
262
263             if (dynObj != null)
264             {
265                 parent = new JsonObject();
266                 Stack<CreateFromTypeStackInfo> infoStack = new Stack<CreateFromTypeStackInfo>();
267                 IEnumerator<string> keys = null;
268
269                 do
270                 {
271                     if (keys == null)
272                     {
273                         keys = dynObj.GetDynamicMemberNames().GetEnumerator();
274                     }
275
276                     while (keys.MoveNext())
277                     {
278                         JsonValue child = null;
279                         string key = keys.Current;
280                         SimpleGetMemberBinder binder = new SimpleGetMemberBinder(key);
281
282                         if (dynObj.TryGetMember(binder, out value))
283                         {
284                             DynamicObject childDynObj = value as DynamicObject;
285
286                             if (childDynObj != null)
287                             {
288                                 child = new JsonObject();
289                                 parent.Add(key, child);
290
291                                 infoStack.Push(new CreateFromTypeStackInfo(parent, dynObj, keys));
292
293                                 parent = child as JsonObject;
294                                 dynObj = childDynObj;
295                                 keys = null;
296
297                                 break;
298                             }
299                             else
300                             {
301                                 if (value != null)
302                                 {
303                                     child = value as JsonValue;
304
305                                     if (child == null)
306                                     {
307                                         child = JsonValueExtensions.CreatePrimitive(value);
308
309                                         if (child == null)
310                                         {
311                                             child = JsonValueExtensions.CreateFromComplex(value);
312                                         }
313                                     }
314                                 }
315
316                                 parent.Add(key, child);
317                             }
318                         }
319                     }
320
321                     if (infoStack.Count > 0 && keys != null)
322                     {
323                         CreateFromTypeStackInfo info = infoStack.Pop();
324
325                         parent = info.JsonObject;
326                         dynObj = info.DynamicObject;
327                         keys = info.Keys;
328                     }
329                 }
330                 while (infoStack.Count > 0);
331             }
332
333             return parent;
334         }
335
336         private class CreateFromTypeStackInfo
337         {
338             public CreateFromTypeStackInfo(JsonObject jsonObject, DynamicObject dynamicObject, IEnumerator<string> keyEnumerator)
339             {
340                 JsonObject = jsonObject;
341                 DynamicObject = dynamicObject;
342                 Keys = keyEnumerator;
343             }
344
345             /// <summary>
346             /// Gets of sets
347             /// </summary>
348             public JsonObject JsonObject { get; set; }
349
350             /// <summary>
351             /// Gets of sets
352             /// </summary>
353             public DynamicObject DynamicObject { get; set; }
354
355             /// <summary>
356             /// Gets of sets
357             /// </summary>
358             public IEnumerator<string> Keys { get; set; }
359         }
360
361         private class SimpleGetMemberBinder : GetMemberBinder
362         {
363             public SimpleGetMemberBinder(string name)
364                 : base(name, false)
365             {
366             }
367
368             public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, DynamicMetaObject errorSuggestion)
369             {
370                 if (target != null && errorSuggestion == null)
371                 {
372                     string exceptionMessage = RS.Format(System.Json.Properties.Resources.DynamicPropertyNotDefined, target.LimitType, Name);
373                     Expression throwExpression = Expression.Throw(Expression.Constant(new InvalidOperationException(exceptionMessage)), typeof(object));
374
375                     errorSuggestion = new DynamicMetaObject(throwExpression, target.Restrictions);
376                 }
377
378                 return errorSuggestion;
379             }
380         }
381     }
382 }
383 #endif