I've spent some time writing a library to parse JSON into a statically typed object. Am I following most of the standard coding guidelines? Any thoughts on what I could do better, or any edge cases that are missing and aren't taken care of?
private static T ParseDynamic(ExpandoObject input)
{
//Parse when given an ExpandoObject
T output = default(T);
var dict = input as IDictionary<string, object>;
ParseDictionary<T>(dict, out output);
return output;
}
protected static void ParseDictionary(IDictionary<string, object> Dict, out object Target, Type explicitType)
{
if(Dict == null)
{
throw new InvalidOperationException("Dictionary was null, cannot parse a null dictionary");
}
if (explicitType.IsArray)
{
var length = Dict.Keys.Count();
Target = (Array)Activator.CreateInstance(explicitType, new object[] { length });
}
else
{
Target = Activator.CreateInstance(explicitType);
}
foreach (var property in Target.GetType().GetProperties())
{
var propertyName = property.Name;
if (Dict.ContainsKey(propertyName) && Dict[propertyName] != null)
{
var val = Dict[propertyName];
var propertyVal = explicitType.GetProperty(propertyName);
var expectedType = property.PropertyType;
var valType = val.GetType();
if(valType == expectedType)
{
//Hurray, we matched!
propertyVal.SetValue(Target, val);
}
else if (valType != expectedType && val is IConvertible)
{
Type safeType = Nullable.GetUnderlyingType(expectedType) ?? expectedType;
//Special Case - INT64 to DATETIME Conversion (UNIX Time)
if((valType == typeof(long) || valType == typeof(long?))
&& (safeType == typeof(DateTime) || safeType == typeof(DateTime?)))
{
var longValue = (long)Convert.ChangeType(val, typeof(long));
var dateValue = UNIX_EPOCH.AddSeconds(longValue);
val = dateValue;
}
//Convert if possible
var explicitVal = (val == null ? null : Convert.ChangeType(val, safeType));
propertyVal.SetValue(Target, explicitVal, null);
}
else if (val is IDictionary<string, object>)
{
//Parse non-simple object
var propType = propertyVal.PropertyType;
object explicitVal = Activator.CreateInstance(propType);
ParseDictionary(val as IDictionary<string, object>, out explicitVal, propType);
propertyVal.SetValue(Target, explicitVal);
}
else if (val is IList)
{
//Parse list/enumeration/array
Type elementType;
if (expectedType.IsArray)
{
//Array type is explicitly included with GetElementType
elementType = expectedType.GetElementType();
}
else if (expectedType.IsGenericType)
{
//Get List type by inspecting generic argument
elementType = expectedType.GetGenericArguments()[0];
}
else
{
//Not sure how we'd get here if we're neither an array nor generic, but we can't really do much
continue;
}
//Create the necessary List implementation that we need
var listType = typeof(List<>);
var typedList = listType.MakeGenericType(elementType);
var explicitList = (IList)Activator.CreateInstance(typedList);
foreach(var element in val as IList<object>)
{
object explicitElement;
ParseDictionary(element as IDictionary<string, object>, out explicitElement, elementType);
explicitList.Add(explicitElement);
}
if(property.PropertyType.IsArray)
{
//Convert from list to array if necessary
var arrayType = elementType.MakeArrayType();
var array = (Array)Activator.CreateInstance(arrayType, new object[] { explicitList.Count });
explicitList.CopyTo(array, 0);
propertyVal.SetValue(Target, array);
}
else
{
propertyVal.SetValue(Target, explicitList);
}
}
else
{
//Attempt to set it - will error if not compatible and all other checks are bypassed
propertyVal.SetValue(Target, val);
}
}
}
}
protected static void ParseDictionary<K>(IDictionary<string, object> Dict, out K Target) where K : class, new()
{
Target = new K();
var explicitType = Target.GetType();
var outObject = new object();
ParseDictionary(Dict, out outObject, explicitType);
Target = outObject as K;
}