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