// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. #if FEATURE_DYNAMIC using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Dynamic; using System.IO; using System.Json; using System.Linq.Expressions; namespace System.Runtime.Serialization.Json { /// /// This class extends the functionality of the type. /// [EditorBrowsable(EditorBrowsableState.Never)] public static class JsonValueExtensions { /// /// Creates a object based on an arbitrary CLR object. /// /// The object to be converted to . /// The which represents the given object. /// The conversion is done through the ; /// the object is first serialized into JSON using the serializer, then parsed into a /// object. public static JsonValue CreateFrom(object value) { JsonValue jsonValue = null; if (value != null) { jsonValue = value as JsonValue; if (jsonValue == null) { jsonValue = JsonValueExtensions.CreatePrimitive(value); if (jsonValue == null) { jsonValue = JsonValueExtensions.CreateFromDynamic(value); if (jsonValue == null) { jsonValue = JsonValueExtensions.CreateFromComplex(value); } } } } return jsonValue; } /// /// Attempts to convert this instance into the type T. /// /// The type to which the conversion is being performed. /// The instance this method extension is to be applied to. /// An instance of T initialized with this instance, or the default /// value of T, if the conversion cannot be performed. /// true if this instance can be read as type T; otherwise, false. public static bool TryReadAsType(this JsonValue jsonValue, out T valueOfT) { if (jsonValue == null) { throw new ArgumentNullException("jsonValue"); } object value; if (JsonValueExtensions.TryReadAsType(jsonValue, typeof(T), out value)) { valueOfT = (T)value; return true; } valueOfT = default(T); return false; } /// /// Attempts to convert this instance into the type T. /// /// The type to which the conversion is being performed. /// The instance this method extension is to be applied to. /// An instance of T initialized with the value /// specified if the conversion. /// If this value cannot be /// converted into the type T. [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "The generic parameter is used to specify the output type")] public static T ReadAsType(this JsonValue jsonValue) { if (jsonValue == null) { throw new ArgumentNullException("jsonValue"); } return (T)JsonValueExtensions.ReadAsType(jsonValue, typeof(T)); } /// /// Attempts to convert this instance into the type T, returning a fallback value /// if the conversion fails. /// /// The type to which the conversion is being performed. /// The instance this method extension is to be applied to. /// A fallback value to be retuned in case the conversion cannot be performed. /// An instance of T initialized with the value /// specified if the conversion succeeds or the specified fallback value if it fails. public static T ReadAsType(this JsonValue jsonValue, T fallback) { if (jsonValue == null) { throw new ArgumentNullException("jsonValue"); } T outVal; if (JsonValueExtensions.TryReadAsType(jsonValue, out outVal)) { return outVal; } return fallback; } /// /// Attempts to convert this instance into an instance of the specified type. /// /// The instance this method extension is to be applied to. /// The type to which the conversion is being performed. /// An object instance initialized with the value /// specified if the conversion. /// If this value cannot be /// converted into the type T. public static object ReadAsType(this JsonValue jsonValue, Type type) { if (jsonValue == null) { throw new ArgumentNullException("jsonValue"); } if (type == null) { throw new ArgumentNullException("type"); } object result; if (JsonValueExtensions.TryReadAsType(jsonValue, type, out result)) { return result; } throw new NotSupportedException(RS.Format(System.Json.Properties.Resources.CannotReadAsType, jsonValue.GetType().FullName, type.FullName)); } /// /// Attempts to convert this instance into an instance of the specified type. /// /// The instance this method extension is to be applied to. /// The type to which the conversion is being performed. /// An object to be initialized with this instance or null if the conversion cannot be performed. /// true if this instance can be read as the specified type; otherwise, false. [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification = "This is the non-generic version of the method.")] [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exception translates to fail.")] public static bool TryReadAsType(this JsonValue jsonValue, Type type, out object value) { if (jsonValue == null) { throw new ArgumentNullException("jsonValue"); } if (type == null) { throw new ArgumentNullException("type"); } if (type == typeof(JsonValue) || type == typeof(object)) { value = jsonValue; return true; } if (type == typeof(object[]) || type == typeof(Dictionary)) { if (!JsonValueExtensions.CanConvertToClrCollection(jsonValue, type)) { value = null; return false; } } if (jsonValue.TryReadAs(type, out value)) { return true; } try { using (MemoryStream ms = new MemoryStream()) { jsonValue.Save(ms); ms.Position = 0; DataContractJsonSerializer dcjs = new DataContractJsonSerializer(type); value = dcjs.ReadObject(ms); } return true; } catch (Exception) { value = null; return false; } } /// /// Determines whether the specified instance can be converted to the specified collection . /// /// The instance to be converted. /// The collection type to convert the instance to. /// true if the instance can be converted, false otherwise private static bool CanConvertToClrCollection(JsonValue jsonValue, Type collectionType) { if (jsonValue != null) { return (jsonValue.JsonType == JsonType.Object && collectionType == typeof(Dictionary)) || (jsonValue.JsonType == JsonType.Array && collectionType == typeof(object[])); } return false; } private static JsonValue CreatePrimitive(object value) { JsonPrimitive jsonPrimitive; if (JsonPrimitive.TryCreate(value, out jsonPrimitive)) { return jsonPrimitive; } return null; } private static JsonValue CreateFromComplex(object value) { DataContractJsonSerializer dcjs = new DataContractJsonSerializer(value.GetType()); using (MemoryStream ms = new MemoryStream()) { dcjs.WriteObject(ms, value); ms.Position = 0; return JsonValue.Load(ms); } } [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "value is not the same")] private static JsonValue CreateFromDynamic(object value) { JsonObject parent = null; DynamicObject dynObj = value as DynamicObject; if (dynObj != null) { parent = new JsonObject(); Stack infoStack = new Stack(); IEnumerator keys = null; do { if (keys == null) { keys = dynObj.GetDynamicMemberNames().GetEnumerator(); } while (keys.MoveNext()) { JsonValue child = null; string key = keys.Current; SimpleGetMemberBinder binder = new SimpleGetMemberBinder(key); if (dynObj.TryGetMember(binder, out value)) { DynamicObject childDynObj = value as DynamicObject; if (childDynObj != null) { child = new JsonObject(); parent.Add(key, child); infoStack.Push(new CreateFromTypeStackInfo(parent, dynObj, keys)); parent = child as JsonObject; dynObj = childDynObj; keys = null; break; } else { if (value != null) { child = value as JsonValue; if (child == null) { child = JsonValueExtensions.CreatePrimitive(value); if (child == null) { child = JsonValueExtensions.CreateFromComplex(value); } } } parent.Add(key, child); } } } if (infoStack.Count > 0 && keys != null) { CreateFromTypeStackInfo info = infoStack.Pop(); parent = info.JsonObject; dynObj = info.DynamicObject; keys = info.Keys; } } while (infoStack.Count > 0); } return parent; } private class CreateFromTypeStackInfo { public CreateFromTypeStackInfo(JsonObject jsonObject, DynamicObject dynamicObject, IEnumerator keyEnumerator) { JsonObject = jsonObject; DynamicObject = dynamicObject; Keys = keyEnumerator; } /// /// Gets of sets /// public JsonObject JsonObject { get; set; } /// /// Gets of sets /// public DynamicObject DynamicObject { get; set; } /// /// Gets of sets /// public IEnumerator Keys { get; set; } } private class SimpleGetMemberBinder : GetMemberBinder { public SimpleGetMemberBinder(string name) : base(name, false) { } public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, DynamicMetaObject errorSuggestion) { if (target != null && errorSuggestion == null) { string exceptionMessage = RS.Format(System.Json.Properties.Resources.DynamicPropertyNotDefined, target.LimitType, Name); Expression throwExpression = Expression.Throw(Expression.Constant(new InvalidOperationException(exceptionMessage)), typeof(object)); errorSuggestion = new DynamicMetaObject(throwExpression, target.Restrictions); } return errorSuggestion; } } } } #endif