Line data Source code
1 : // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
2 :
3 : using System.Collections;
4 : using System.Collections.Generic;
5 : using System.IO;
6 : using System.Web.Script.Serialization;
7 : using System.Diagnostics;
8 : using System.Diagnostics.CodeAnalysis;
9 : using System.Dynamic;
10 : using System.Globalization;
11 : using System.Linq;
12 : using System.Linq.Expressions;
13 : using System.Runtime.CompilerServices;
14 : using System.Web.Helpers.Resources;
15 : using Microsoft.CSharp.RuntimeBinder;
16 :
17 : // ReSharper disable once CheckNamespace
18 : namespace System.Web.Helpers
19 : {
20 : public static class Json
21 0 : {
22 : private static readonly JavaScriptSerializer _serializer = CreateSerializer();
23 :
24 0 : public static string Encode(object value)
25 : {
26 : // Serialize our dynamic array type as an array
27 : DynamicJsonArray jsonArray = value as DynamicJsonArray;
28 : if (jsonArray != null)
29 : {
30 : return _serializer.Serialize((object[])jsonArray);
31 : }
32 :
33 : return _serializer.Serialize(value);
34 : }
35 :
36 0 : public static void Write(object value, TextWriter writer)
37 : {
38 : writer.Write(_serializer.Serialize(value));
39 : }
40 :
41 0 : public static dynamic Decode(string value)
42 : {
43 : return WrapObject(_serializer.DeserializeObject(value));
44 : }
45 :
46 0 : public static dynamic Decode(string value, Type targetType)
47 : {
48 : return WrapObject(_serializer.Deserialize(value, targetType));
49 : }
50 :
51 0 : public static T Decode<T>(string value)
52 : {
53 : return _serializer.Deserialize<T>(value);
54 : }
55 :
56 : private static JavaScriptSerializer CreateSerializer()
57 : {
58 : JavaScriptSerializer serializer = new JavaScriptSerializer();
59 : serializer.RegisterConverters(new[] { new DynamicJavaScriptConverter() });
60 : return serializer;
61 : }
62 :
63 : internal static dynamic WrapObject(object value)
64 : {
65 : // The JavaScriptSerializer returns IDictionary<string, object> for objects
66 : // and object[] for arrays, so we wrap those in different dynamic objects
67 : // so we can access the object graph using dynamic
68 : var dictionaryValues = value as IDictionary<string, object>;
69 : if (dictionaryValues != null)
70 : {
71 : return new DynamicJsonObject(dictionaryValues);
72 : }
73 :
74 : var arrayValues = value as object[];
75 : if (arrayValues != null)
76 : {
77 : return new DynamicJsonArray(arrayValues);
78 : }
79 :
80 : return value;
81 : }
82 : }
83 :
84 : // REVIEW: Consider implementing ICustomTypeDescriptor and IDictionary<string, object>
85 : public class DynamicJsonObject : DynamicObject
86 0 : {
87 : private readonly IDictionary<string, object> _values;
88 :
89 0 : public DynamicJsonObject(IDictionary<string, object> values)
90 : {
91 : Debug.Assert(values != null);
92 : _values = values.ToDictionary(p => p.Key, p => Json.WrapObject(p.Value),
93 : StringComparer.OrdinalIgnoreCase);
94 : }
95 :
96 0 : public override bool TryConvert(ConvertBinder binder, out object result)
97 : {
98 : result = null;
99 : if (binder.Type.IsAssignableFrom(_values.GetType()))
100 : {
101 : result = _values;
102 : }
103 : else
104 : {
105 : throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, HelpersResources.Json_UnableToConvertType, binder.Type));
106 : }
107 : return true;
108 : }
109 :
110 0 : public override bool TryGetMember(GetMemberBinder binder, out object result)
111 : {
112 : result = GetValue(binder.Name);
113 : return true;
114 : }
115 :
116 0 : public override bool TrySetMember(SetMemberBinder binder, object value)
117 : {
118 : _values[binder.Name] = Json.WrapObject(value);
119 : return true;
120 : }
121 :
122 0 : public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)
123 : {
124 : string key = GetKey(indexes);
125 : if (!String.IsNullOrEmpty(key))
126 : {
127 : _values[key] = Json.WrapObject(value);
128 : }
129 : return true;
130 : }
131 :
132 0 : public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
133 : {
134 : string key = GetKey(indexes);
135 : result = null;
136 : if (!String.IsNullOrEmpty(key))
137 : {
138 : result = GetValue(key);
139 : }
140 : return true;
141 : }
142 :
143 : private static string GetKey(object[] indexes)
144 : {
145 : if (indexes.Length == 1)
146 : {
147 : return (string)indexes[0];
148 : }
149 : // REVIEW: Should this throw?
150 : return null;
151 : }
152 :
153 0 : public override IEnumerable<string> GetDynamicMemberNames()
154 : {
155 : return _values.Keys;
156 : }
157 :
158 : private object GetValue(string name)
159 : {
160 : object result;
161 : if (_values.TryGetValue(name, out result))
162 : {
163 : return result;
164 : }
165 : return null;
166 : }
167 : }
168 :
169 : [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification = "This class isn't meant to be used directly")]
170 : public class DynamicJsonArray : DynamicObject, IEnumerable<object>
171 0 : {
172 : private readonly object[] _arrayValues;
173 :
174 0 : public DynamicJsonArray(object[] arrayValues)
175 : {
176 : Debug.Assert(arrayValues != null);
177 : _arrayValues = arrayValues.Select(Json.WrapObject).ToArray();
178 : }
179 :
180 : public int Length
181 : {
182 : get { return _arrayValues.Length; }
183 : }
184 :
185 : public dynamic this[int index]
186 : {
187 : get { return _arrayValues[index]; }
188 : set { _arrayValues[index] = Json.WrapObject(value); }
189 : }
190 :
191 0 : public override bool TryConvert(ConvertBinder binder, out object result)
192 : {
193 : if (_arrayValues.GetType().IsAssignableFrom(binder.Type))
194 : {
195 : result = _arrayValues;
196 : return true;
197 : }
198 : return base.TryConvert(binder, out result);
199 : }
200 :
201 0 : public override bool TryGetMember(GetMemberBinder binder, out object result)
202 : {
203 : // Testing for members should never throw. This is important when dealing with
204 : // services that return different json results. Testing for a member shouldn't throw,
205 : // it should just return null (or undefined)
206 : result = null;
207 : return true;
208 : }
209 :
210 0 : public IEnumerator GetEnumerator()
211 : {
212 : return _arrayValues.GetEnumerator();
213 : }
214 :
215 : private IEnumerable<object> GetEnumerable()
216 : {
217 : return _arrayValues.AsEnumerable();
218 : }
219 :
220 : IEnumerator<object> IEnumerable<object>.GetEnumerator()
221 : {
222 : return GetEnumerable().GetEnumerator();
223 : }
224 :
225 : [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "This class isn't meant to be used directly")]
226 0 : public static implicit operator object[](DynamicJsonArray obj)
227 : {
228 : return obj._arrayValues;
229 : }
230 :
231 : [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "This class isn't meant to be used directly")]
232 0 : public static implicit operator Array(DynamicJsonArray obj)
233 : {
234 : return obj._arrayValues;
235 : }
236 : }
237 :
238 : /// <summary>
239 : /// Converter that knows how to get the member values from a dynamic object.
240 : /// </summary>
241 : internal class DynamicJavaScriptConverter : JavaScriptConverter
242 : {
243 : public override IEnumerable<Type> SupportedTypes
244 : {
245 : get
246 : {
247 : // REVIEW: For some reason the converters don't pick up interfaces
248 : yield return typeof(IDynamicMetaObjectProvider);
249 : yield return typeof(DynamicObject);
250 : }
251 : }
252 :
253 0 : public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
254 : {
255 : throw new NotSupportedException();
256 : }
257 :
258 0 : public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
259 : {
260 : var values = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
261 : var memberNames = DynamicHelper.GetMemberNames(obj);
262 :
263 : // This should never happen
264 : Debug.Assert(memberNames != null);
265 :
266 : // Get the value for each member in the dynamic object
267 : foreach (string memberName in memberNames)
268 : {
269 : values[memberName] = DynamicHelper.GetMemberValue(obj, memberName);
270 : }
271 :
272 : return values;
273 : }
274 : }
275 :
276 : /// <summary>
277 : /// Helper to evaluate different method on dynamic objects
278 : /// </summary>
279 : internal static class DynamicHelper
280 : {
281 : // We must pass in "object" instead of "dynamic" for the target dynamic object because if we use dynamic, the compiler will
282 : // convert the call to this helper into a dynamic expression, even though we don't need it to be. Since this class is internal,
283 : // it cannot be accessed from a dynamic expression and thus we get errors.
284 :
285 : // Dev10 Bug 914027 - Changed the first parameter from dynamic to object, see comment at top for details
286 0 : public static bool TryGetMemberValue(object obj, string memberName, out object result)
287 : {
288 : try
289 : {
290 : result = GetMemberValue(obj, memberName);
291 : return true;
292 : }
293 : catch (RuntimeBinderException)
294 : {
295 : }
296 : catch (RuntimeBinderInternalCompilerException)
297 : {
298 : }
299 :
300 : // We catch the C# specific runtime binder exceptions since we're using the C# binder in this case
301 : result = null;
302 : return false;
303 : }
304 :
305 : // Dev10 Bug 914027 - Changed the first parameter from dynamic to object, see comment at top for details
306 : [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We want to swallow exceptions that happen during runtime binding")]
307 0 : public static bool TryGetMemberValue(object obj, GetMemberBinder binder, out object result)
308 : {
309 : try
310 : {
311 : // VB us an instance of GetBinderAdapter that does not implement FallbackGetMemeber. This causes lookup of property expressions on dynamic objects to fail.
312 : // Since all types are private to the assembly, we assume that as long as they belong to CSharp runtime, it is the right one.
313 : if (typeof(Binder).Assembly.Equals(binder.GetType().Assembly))
314 : {
315 : // Only use the binder if its a C# binder.
316 : result = GetMemberValue(obj, binder);
317 : }
318 : else
319 : {
320 : result = GetMemberValue(obj, binder.Name);
321 : }
322 : return true;
323 : }
324 : catch
325 : {
326 : result = null;
327 : return false;
328 : }
329 : }
330 :
331 : // Dev10 Bug 914027 - Changed the first parameter from dynamic to object, see comment at top for details
332 0 : public static object GetMemberValue(object obj, string memberName)
333 : {
334 : var callSite = GetMemberAccessCallSite(memberName);
335 : return callSite.Target(callSite, obj);
336 : }
337 :
338 : // Dev10 Bug 914027 - Changed the first parameter from dynamic to object, see comment at top for details
339 0 : public static object GetMemberValue(object obj, GetMemberBinder binder)
340 : {
341 : var callSite = GetMemberAccessCallSite(binder);
342 : return callSite.Target(callSite, obj);
343 : }
344 :
345 : // dynamic d = new object();
346 : // object s = d.Name;
347 : // The following code gets generated for this expression:
348 : // callSite = CallSite<Func<CallSite, object, object>>.Create(Binder.GetMember(CSharpBinderFlags.None, "Name", typeof(Program), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }));
349 : // callSite.Target(callSite, d);
350 : // typeof(Program) is the containing type of the dynamic operation.
351 : // Dev10 Bug 914027 - Changed the callsite's target parameter from dynamic to object, see comment at top for details
352 0 : public static CallSite<Func<CallSite, object, object>> GetMemberAccessCallSite(string memberName)
353 : {
354 : var binder = Binder.GetMember(CSharpBinderFlags.None, memberName, typeof(DynamicHelper), new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) });
355 : return GetMemberAccessCallSite(binder);
356 : }
357 :
358 : // Dev10 Bug 914027 - Changed the callsite's target parameter from dynamic to object, see comment at top for details
359 0 : public static CallSite<Func<CallSite, object, object>> GetMemberAccessCallSite(CallSiteBinder binder)
360 : {
361 : return CallSite<Func<CallSite, object, object>>.Create(binder);
362 : }
363 :
364 : // Dev10 Bug 914027 - Changed the first parameter from dynamic to object, see comment at top for details
365 0 : public static IEnumerable<string> GetMemberNames(object obj)
366 : {
367 : var provider = obj as IDynamicMetaObjectProvider;
368 : Debug.Assert(provider != null, "obj doesn't implement IDynamicMetaObjectProvider");
369 :
370 : Expression parameter = Expression.Parameter(typeof(object));
371 : return provider.GetMetaObject(parameter).GetDynamicMemberNames();
372 : }
373 : }
374 : }
|